LDOI 題解合集

FBIWZH發表於2024-03-31

T1 不知所云

  • 對於 \(10\%\) 的資料:純暴力

  • 對於 \(30\%\) 的資料:

    我們稍微思考一下:對於 \(a_i\),它產生逆序對的個數就是它前面比它大的數的個數加上它後面比它小的數的個數。而總的逆序對的個數減去它產生逆序對的個數就是刪除它後數列的逆序對個數。最後求最值即可。

  • 對於 \(100\%\) 的資料:

    有兩種思路。

    第一種:我們可以最佳化 \(30\%\) 的做法,把暴力數改成用歸併求。

    #include<bits/stdc++.h>
    using namespace std;
    inline int read() {
    	    int ret=0,f=1;
    	    char ch=getchar();
    	    while (ch<'0'||ch>'9') {
    	  	    if (ch=='-') f=-f;
    		    ch=getchar();
    	    }
    	    while (ch>='0'&&ch<='9') ret=ret*10+(ch^48),ch=getchar();
    	    return ret*f;
    }
    #define N 1000005
    int input[N],tmp[N],a[N];
    long long res[N],res2[N],s,maxx=LONG_LONG_MAX;
    void merge(int l,int mid,int r) {
    		int i=l,j=mid+1,k=1;
    		while (i<=mid&&j<=r) {
    			if (a[input[i]]>a[input[j]]) {
    				res[input[j]] += mid - i + 1;
    				tmp[k++]=input[j++];
    			} else tmp[k++]=input[i++];
    		}
    		while(i<=mid)tmp[k++]=input[i++];
    		while(j<=r)tmp[k++]=input[j++];
    		for (i=l,k=1; i<=r; i++,k++)input[i]=tmp[k];
    }
    void merge_sort(int l,int r) {
    		if(l<r) {
    			int mid=(l+r)/2;
    			merge_sort(l,mid);
    			merge_sort(mid+1,r);
    			merge(l,mid,r);
    		}
    }
    void merge2(int l,int mid,int r) {
    		int i=l,j=mid+1,k=1;
    		while (i<=mid&&j<=r) {
    			if (a[input[i]]<a[input[j]]) {
    				res2[input[j]] += mid - i + 1;
    				tmp[k++]=input[j++];
    			} else tmp[k++]=input[i++];
    		}
    		while(i<=mid)tmp[k++]=input[i++];
    		while(j<=r)tmp[k++]=input[j++];
    		for (i=l,k=1; i<=r; i++,k++)input[i]=tmp[k];
    }
    void merge_sort2(int l,int r) {
    		if(l<r) {
    			int mid=(l+r)/2;
    			merge_sort2(l,mid);
    			merge_sort2(mid+1,r);
    			merge2(l,mid,r);
    		}
    }
    main() 
    {
    		int n=read(),maxi=0;
    		/*
    		input 陣列存索引,這樣才能找到原來的位置
    		*/
    		for(int i=1; i<=n; i++)input[i]=i, a[i]=read();
    		merge_sort(1,n);
    		for(int i=1; i<=n; i++)input[i]=n-i+1, s+=res[i];
    		merge_sort2(1,n);
    		for(int i=1; i<=n; i++)
    			if(res[i]+res2[i]<maxx)
    				maxx=res[i]+res2[i],maxi=i;
    		cout<<s-maxx<<' '<<maxi<<endl;
    		return 0;
    }
    

    你也可以用樹狀陣列:

    #include<bits/stdc++.h>
    #define lowbit(x) ((x)&-(x))
    using namespace std;
    int n,a[1000010];
    int t[50005]; // 樹狀陣列
    inline int query(int x) {
    		int sum=0;
    		while(x) sum+=t[x], x-=lowbit(x);
    		return sum;
    }
    inline void add(int x, int v) {
    		while(x<=50000) t[x]+=v, x+=lowbit(x);
    }
    int c[1000005];
    int main() {
    		scanf("%d",&n);
    		for(int i=1; i<=n; i++) scanf("%d",&a[i]);
    		for(int i=1; i<=n; i++) c[i]+=query(a[i]), add(1,1), add(a[i],-1);
    		memset(t,0,sizeof t);
    		for(int i=n; i>=1; i--) c[i]+=query(a[i]), add(a[i]+1,1);
    		long long sumval=0;
    		int minval=1e9,mini;
    		for(int i=1; i<=n; i++){
    			sumval += c[i];
    			if(c[i]<minval) minval=c[i], mini=i;
    		}
    		printf("%lld %d",(sumval>>1)-minval,mini);
    		return 0;
    }
    

T4 異或數對

Preface

好題。

Solution

對於 $ \forall (a,b)$,若其是靚點二元組的充要條件為:

  • \(b\) 的二進位制位數小於 \(a\)
  • \(b\) 的最高位對應的 \(a\) 這一位為 \(0\)(二進位制)。

為什麼?

第一條是為了保證順序(即 \(i<j\))。

第二條則是真正的關鍵。由於 \(b\) 的最高位必定為 \(1\),若 \(b\) 的最高位對應的 \(a\) 這一位為 \(1\),則那一位異或後的結果必定為 \(0\),而 \(a\) 那一位原來為 \(1\),因此導致 \(a\ \mathrm{xor}\ b\) 必定小於 \(a\),又由第一條知 \(a>b\)\(\therefore a\ \mathrm{xor}\ b<max\{a,b\}\),違背題意。

如何實現?

我們可以開設兩個桶 \(t,p\),對於每一個 \(a_i\),把它的二進位制分離出來,列舉每一位。如果對於從右到左的第 \(i\) 位為 \(0\),則 \(t_i+1\to t_i\);如果對於從右到左的第 \(i\) 位為 \(1\),則 \(p_i+1\to p_i\)

這樣,從 \(1\) 列舉到 \(\log_2{\mathrm{max}\{a_i\}}+1\),把 \(ans\) 不斷加上 \(t_i\times p_i\) 即是答案。

時間複雜度 \(O(n\times\log_2{\mathrm{max}\{a_i\}})\),空間複雜度 \(O(n+\log_2{\mathrm{max}\{a_i\}})\)

AC-Code:

#include <bits/stdc++.h>
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}
const int N = 3e6+5,add=1,del=-1;
long long a[N],b[305],c[305],n,ans,Q,x,y;
void change(long long num,int op) {
	int k=1;
	while(num) {
		if(!(num&1))
			b[k]+=op;
		k++,num>>=1;
	}
	c[k-1]+=op;
}
int main() {
	int n=read(),Q=read(); 
	for (int i=1;i<=n;i++) {
		a[i]=read();
		change(a[i],add);
	}
	while(Q--) {
		x=read(),y=read();
		change(a[x],del);
		change(y,add);
		a[x]=y;
		ans=0;
		for(int i=1;i<=32;i++)
			ans+=b[i]*c[i];
		printf("%d\n",ans); 
	}
	return 0;
}

T3 管道迷宮

Solution

先來分析題目,題目要求在時間最短的情況下最小的 \(x_{max}\)

如何讓時間最小?

顯然,當 \(x=n\) 時,時間 \(t\) 一定最短。

由此,我們不妨取 \(x=n\),然後 bfs 一遍,求出 \(t_{min}\)

之後,我們在 \([1,n]\) 之間對 \(x\) 二分答案。用 bfs 作為 check 函式,如果對於一個 \(x\) 時間 \(t \ne t_{min}\),那麼就往大了試。

由此即可,時間複雜度 \(O(n^2\log_2n)\)

AC-Code

#include<bits/stdc++.h>
using namespace std;

inline int read() {
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
	return x*f;
}
struct node {
	int x,y,t;
} top,nd;
int dx[]= {1,-1,0,0};
int dy[]= {0,0,-1,1};
char a[1010][1010];
bool vis[1010][1010];
node q[80000000];
#define push(u) [front++]=u
int n,m,front=1,tail=0;
int check(int xmax) {
	memset(vis,0,sizeof(vis));
	top.x=1,top.y=1,top.t=0;
	q push(top);
	while(front>tail) {
		top=q[++tail];
		int sx=top.x,sy=top.y,sti=top.t;
		if(sx==n&&sy==m) return sti;
		if(a[sx][sy]=='.'||a[sx][sy]=='&')
			for(int i=0; i<4; i++) {
				int nx=sx+dx[i],ny=sy+dy[i];
				if(nx<1||nx>n||ny<1||ny>m) continue;
				if(a[nx][ny]=='#') continue;
				if(vis[nx][ny]) continue;
				nd.x=nx,nd.y=ny,nd.t=sti+1,q push(nd),vis[nx][ny]=1;
			}
		else if(isalpha(a[sx][sy])) {
			switch(a[sx][sy]) {
				case 'U':
					for(int i=0; i<=xmax; ++i) {
						int nx=sx-i,ny=sy;
						if(nx<1||nx>n||ny<1||ny>m) break;
						if(a[nx][ny]=='#') break;
						if(vis[nx][ny]) continue;
						nd.x=nx,nd.y=ny,nd.t=sti+1,q push(nd),vis[nx][ny]=1;
						if(a[nx][ny]=='&') break;
					}
					break;
				case 'D':
					for(int i=0; i<=xmax; ++i) {
						int nx=sx+i,ny=sy;
						if(nx<1||nx>n||ny<1||ny>m) break;
						if(a[nx][ny]=='#') break;
						if(vis[nx][ny]) continue;
						nd.x=nx,nd.y=ny,nd.t=sti+1,q push(nd),vis[nx][ny]=1;
						if(a[nx][ny]=='&') break;
					}
					break;
				case 'L':
					for(int i=0; i<=xmax; ++i) {
						int nx=sx,ny=sy-i;
						if(nx<1||nx>n||ny<1||ny>m) break;
						if(a[nx][ny]=='#') break;
						if(vis[nx][ny]) continue;
						nd.x=nx,nd.y=ny,nd.t=sti+1,q push(nd),vis[nx][ny]=1;
						if(a[nx][ny]=='&') break;
					}
					break;
				case 'R':
					for(int i=0; i<=xmax; ++i) {
						int nx=sx,ny=sy+i;
						if(nx<1||nx>n||ny<1||ny>m) break;
						if(a[nx][ny]=='#') break;
						if(vis[nx][ny]) continue;
						nd.x=nx,nd.y=ny,nd.t=sti+1,q push(nd),vis[nx][ny]=1;
						if(a[nx][ny]=='&') break;
					}
					break;
			}

		}
	}
	return 1e9+10;
}
int main() 
{
	ios::sync_with_stdio(false);
	cin.tie(NULL),cout.tie(NULL);
	cin>>n>>m;
	for(int i=1; i<=n; ++i) {
		for(int j=1; j<=m; ++j)
			cin>>a[i][j];
	}

	int l=1,r=max(n,m),ans=0,sum=1e9,t;
	while(l<=r) 
	{
		int mid=l+r>>1;
		t=check(mid);
		if(t<=sum) ans=mid,sum=t,r=mid-1;
		else l=mid+1;
	}
	if(sum!=1e9)cout<<ans<<" "<<sum;
	else cout<<-1;
	return 0;
}

T4 物競天擇

其實是一道很顯然的 DP(別看它放在第四題 bushi)。

  • 我們設立一個二維陣列 \(dp\)\(dp[i,j]\) 代表在第 \(j\) 個時間點透過學習 \(i\) 門課程所能獲得的最大收益。
  • 因此,不難看出狀態轉移方程即為 \(dp[i,j]=max\{dp[i-1,j-t_i+1]+v_i\times min(j-t_i,j],dp[i,j]\}(j>=t_i)\),其中 \(t_i\) 為第 \(i\) 個題目所需的時間,\(v_i\) 為第 \(i\) 個題目所有的價值。

最後奉上AC程式碼:

#include<bits/stdc++.h>
using namespace std;
long long T,st[10005][21],n,f[114][11451];
struct data{int v,t;}a[114];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct RMQ
{
	RMQ()
	{
		int col=int(log(T)/log(2));
		for(int j=1;j<=21;j++)
        	for(int i=1;i+(1<<j)-1<=T;i++)
            	st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	}
	inline int Query(int l,int r) 
	{
		int p=(int)(log2(r-l+1));
		int res=min(st[l][p], st[r-(1<<p)+1][p]);
		return res;
	}
}; 

int main()
{
	n=read(),T=read();
	for(int i=1;i<=n;i++)
		a[i].v=read(),a[i].t=read();
	for(int i=1;i<=T;i++)st[i][0]=read();
	RMQ q;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=T;j++)
		{
		    f[i][j]=max(f[i-1][j],f[i][j-1]);
			if(a[i].t<=j)
			    f[i][j]=max(f[i-1][j-a[i].t]+q.Query(j-a[i].t+1,j)*a[i].v,f[i][j]);
			
		}
	cout<<f[n][T];
	return 0;
} 

T5 解不等式

Preface

神說,要有模擬題,就有了大糞模擬。

本題看似數學,實則是一道模擬,不喜勿噴。

悄悄話:我們團隊自己人都懶得驗題。

引理

  • 穿根法

    可以根據不等式零點來判斷 \(x\) 的範圍。下面這篇文章講的很清楚,這裡不再贅述。

    穿針引線法(根軸法)解一元高次不等式的原理是什麼?

  • 因式定理(可以不用)

    是列舉零點的一種方式,實際上,由於值域過小可以不用,列舉 \([-888,888]\) 區間的每一個整數已經夠用。

    具體可以看以下介紹:

思路

思路大致如下:

  • 讀入各項係數。
  • 使用長除法+列舉求出零點。
  • 使用穿根法來輸出。

長除法使用遞迴實現,並不困難:

inline int superdiv(int k) {
	long long tmp=a[n];
	for(int i=n; i>0; i--) b[i-1]=tmp, tmp=a[i-1]-tmp*k;
	return !tmp;
}

難點主要在於穿根法的輸出。

我們慢慢分析,首先是奇數次(穿過零點)的部分:

if(op == 1) { // <=
// 本來在x軸上方,穿到下面,滿足
	if(up == -1) printf("%d<=x",i),f=0;
// 本來在x軸下方,穿到上面,不再滿足
	else if(f == 1) printf("x<=%d ",i);
	else printf("<=%d ",i);
} else if(op == 2) { // <
// 本來在x軸上方,穿到下面,滿足
	if(up == -1) printf("%d<x",i),f=0;
// 本來在x軸下方,穿到上面,不再滿足
	else if(f == 1) printf("x<%d ",i);
	else printf("<%d ",i);
} else if(op == 3) { // >=,同 1
	if(up == 1) printf("%d<=x",i),f=0;
	else if(f == 1) printf("x<=%d ",i);
	else printf("<=%d ",i);
} else if(op == 4) { // >,同 2
	if(up == 1) printf("%d<x",i),f=0;
	else if(f == 1) printf("x<%d ",i);
	else printf("<%d ",i);
}

然後是偶數次(沒有穿過零點,但正好過零點)的部分:

if(op == 1) { // <=
// 本來在x軸上方,有一點落到x軸上,正好滿足
	if(up == 1) printf("x=%d ",i);
} else if(op == 2) { // <
// 本來在x軸下方,有一點落到x軸上,正好不滿足
	if(up == -1) {
		if(f == 1) printf("x<%d ",i);
		else printf("<%d ",i);
		printf("%d<x",i),f=0;
	}
} else if(op == 3) { // >=,同 1
	if(up == -1) printf("x=%d ",i);
} else if(op == 4) { // >,同 2
	if(up == 1) {
		if(f == 1) printf("x<%d ",i);
		else printf("<%d ",i);
		printf("%d<x",i),f=0;
	}
}

最後,全部連起來就 AC 了。

#include<bits/stdc++.h>
using namespace std;
long long a[7], b[7];
int cnt[2005], n;
int vis[10], viscnt=0;
// 長除法除以 (x+k),返回 1 表示整除
inline int superdiv(int k) {
	long long tmp=a[n];
	for(int i=n; i>0; i--) b[i-1]=tmp, tmp=a[i-1]-tmp*k;
	return !tmp;
}
int main() {
	int T;
	cin >> T;
	while(T--) {
		// 初始化
		n=0, viscnt=0, memset(a,0,sizeof a), memset(cnt,0,sizeof cnt);
		char ch=getchar();
		// 跳過空行和回車
		while(ch == '\n' || ch == '\r') ch=getchar();
		// 讀取數字和符號
		while(ch!='\n'&&ch!='<'&&ch!='>') {
			int f=1, xk=0;
			long long ans=0;
			while(!isdigit(ch)) {
				if(ch=='-') f=-f; // 負號
				ch=getchar();
			}
			while(isdigit(ch)) ans=ans*10+(ch^48), ch=getchar();
			ans*=f;
			while(!isdigit(ch)) {
				if(ch=='\n'||ch=='<'||ch=='>') break;
				ch=getchar();
			}
			while(isdigit(ch)) xk=xk*10+(ch^48), ch=getchar();
			a[xk]+=ans, n=max(n,xk);
		}

		int op;
		if(ch=='<') op = (getchar()=='=') ? 1 : 2;
		else op = (getchar()=='=') ? 3 : 4;
		getchar();

		// 正負(穿根)
		int up = (n%2==0) ? 1 : -1;

		// 試根 
		for(int i=-1000; i<=1000;) {
			if(superdiv(i) == 1) {
				if(++cnt[(-i)+1000] == 1) vis[++viscnt]=-i;
				// n<=1,結束判斷
				if(--n == 0) break; 
				if(n == 1) {
					if(++cnt[-b[0]+1000] == 1) vis[++viscnt]=-b[0];
					break;
				}
				for(int i=0; i<=n; i++) a[i]=b[i];
			} else i++; // 試下一個根
		}

		int f=1;

		// 輸出結果
		for(int j=viscnt; j>=1; j--) {
			int i=vis[j];
			if(cnt[i+1000]) {
				if(cnt[i+1000]&1) { // 奇數直接穿過
					up = -up;
					// 判斷符號和輸出
					if(op == 1) { // <= 
						if(up == -1) printf("%d<=x",i),f=0;
						else if(f == 1) printf("x<=%d ",i);
						else printf("<=%d ",i);
					} else if(op == 2) { // <
						if(up == -1) printf("%d<x",i),f=0;
						else if(f == 1) printf("x<%d ",i);
						else printf("<%d ",i);
					} else if(op == 3) { // >=
						if(up == 1) printf("%d<=x",i),f=0;
						else if(f == 1) printf("x<=%d ",i);
						else printf("<=%d ",i);
					} else if(op == 4) { // >
						if(up == 1) printf("%d<x",i),f=0;
						else if(f == 1) printf("x<%d ",i);
						else printf("<%d ",i);
					}
				} else { // 偶數不穿過,但正好過零點
					if(op == 1) { // <= 
						if(up == 1) printf("x=%d ",i);
					} else if(op == 2) { // <
						if(up == -1) {
							if(f == 1) printf("x<%d ",i);
							else printf("<%d ",i);
							printf("%d<x",i),f=0;
						}
					} else if(op == 3) { // >=
						if(up == -1) printf("x=%d ",i);
					} else if(op == 4) { // >
						if(up == 1) {
							if(f == 1) printf("x<%d ",i);
							else printf("<%d ",i);
							printf("%d<x",i),f=0;
						}
					}
				}
			}
		}
		printf("\n");
	}
	return 0;
}
謝謝觀看喵~

相關文章