1. 程式人生 > 其它 >2021省選聯考做題記錄

2021省選聯考做題記錄

不管AB捲了就按洛谷的排列順序來寫吧。

1.卡牌遊戲

問題可以看成是選最多 \(m\)\(b_i\) 所能達到的最佳答案。

直接 \(a,b\) 混在一起排序(當然要記錄下這個數原來是 \(a\) 還是 \(b\) )。然後要做的就是儘量砍掉兩頭的數。

直接雙指標記錄前面刪 \(i\) 個數是後面最多刪多少個數,掃一遍求個最小值就好了。

#include<cstdio>
#include<algorithm>
#define rep(a,b,c) for(int c=(a);c<=(b);++c)
#define drep(a,b,c) for(int c=(a);c>=(b);--c)
inline int read()
{
	int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
inline int Min(int x,int y){return x<y?x:y;} inline int Max(int x,int y){return x>y?x:y;}
const int N=1e6+10; struct Sort
{
	int idx,val;bool flag;
	inline bool operator<(const Sort &x)const{return val<x.val;}
}Q[N<<1];bool vis[N];
int main()
{
	int n=read(),k=read();rep(1,n,i)Q[i].val=read(),Q[i].idx=i,Q[i].flag=1;
	rep(1,n,i)Q[i+n].val=read(),Q[i+n].idx=i,Q[i+n].flag=0;std::sort(Q+1,Q+(n<<1|1));
	int l=1,r=2*n;while(k>=0&&l<2*n)
	{
		if(vis[Q[l].idx])break;
		vis[Q[l].idx]=1;k-=Q[l++].flag;
	}
	if(k<0)k+=Q[--l].flag,vis[Q[l].idx]=0;
	int ans=Q[r].val-Q[l].val; while(l)
	{
		while(l<=r&&k>=0)
		{
			if(vis[Q[r].idx])break;
			vis[Q[r].idx]=1;k-=Q[r--].flag;
		}
		if(k<0)k+=Q[++r].flag,vis[Q[r].idx]=0;
		ans=Min(ans,Q[r].val-Q[l].val);
		k+=Q[--l].flag;vis[Q[l].idx]=0;
	}
	printf("%d\n",ans);
}

2.矩陣遊戲

首先容易構造出一個值域為 \(\Z\)\(a'\) 陣列(直接欽定第一排第一列為 \(0\) 即可),然後考慮調整。

考慮對整行整列進行操作,發現給矩陣奇偶黑白染色後,對整排或整列的黑點 \(+1\) ,白點 \(-1\) 結果仍然合法。

然後設第 \(i\) 行加了 \(r_i\) ,第 \(j\) 列加了 \(c_j\) ,那麼真實的 \(a\) 滿足:

\(a_{i,j}=a'_{i,j}+(r_i-c_j)(i+j\bmod 2==1)\)

\(a_{i,j}=a'_{i,j}-(r_i-c_j)(i+j\bmod 2==0)\)

然後就可以根據 \(a_{i,j}\in[0,10^6]\)

列出差分約束的不等式了。

#include<cstdio>
#include<cstring>
#define rep(a,b,c) for(int c=(a);c<=(b);++c)
typedef long long LL;
#define int LL
inline int read()
{
	int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
inline bool chkmin(int &x,const int &w){return w<x?x=w,true:false;}
inline void Swap(int &x,int &y){int tmp(x);x=y;y=tmp;}
const int N=310,M=610,W=1e6,INF=0x3f3f3f3f;
int a[N][N],b[N][N],G[M][M],dis[M],n,m;
inline bool BellmanFord()
{
	memset(dis,0x3f,sizeof(dis));dis[0]=0;
	int T,flag(true);for(T=0;T<=n+m&&flag;++T)
	{
		flag=false;rep(1,n+m,i)flag|=chkmin(dis[i],dis[0]+G[0][i]);
		rep(1,n,i)rep(n+1,n+m,j)flag|=chkmin(dis[j],dis[i]+G[i][j]);
		rep(n+1,n+m,i)rep(1,n,j)flag|=chkmin(dis[j],dis[i]+G[i][j]);
	}
	return T<n+m;
}
inline void Solve()
{
	n=read();m=read();memset(G,0x3f,sizeof(G));
	rep(2,n,i)rep(2,m,j)a[i][j]=read()-a[i-1][j-1]-a[i-1][j]-a[i][j-1];
	rep(1,n+m,i)G[0][i]=0; rep(1,n,i)rep(1,m,j)
	{
		//-a[i][j]<=r[i]-c[j]<=W-a[i][j]
		G[j+n][i]=W-a[i][j]; //r[i]-c[j]<=W-a[i][j]
		G[i][j+n]=a[i][j];   //c[j]-r[i]<=a[i][j]
		if(!((i^j)&1))Swap(G[j+n][i],G[i][j+n]);
	}
	if(!BellmanFord())return puts("NO"),void();puts("YES");
	rep(1,n,i){rep(1,m,j)printf("%lld ",a[i][j]+(((i^j)&1)?1:-1)*(dis[i]-dis[j+n]));puts("");		}
}
signed main(){int T=read();while(T--)Solve();return 0;}

3.圖函式

遇事不決先找性質,並且相信奇蹟,\(O(n^3)\)\(1000\)

找一找性質,可以發現:一個點 \(x\) 合法當且僅當它僅經過 \(y\in(x,n)\) 的點能夠同時存在 \(i\rightarrow x,x\rightarrow i\) 的路徑。

證:若 \(u\in[0,x)\) 合法,\(u\) 已經被刪除;若不合法,不存在 \(i\rightarrow u\rightarrow i\) ,則不存在 \(i\rightarrow u\rightarrow v\rightarrow i\)\(i\rightarrow v\rightarrow u\rightarrow i\)

然後對於依次加邊統計答案轉化為第 \(i\) 條邊邊權為 \(i\) ,按一條路徑經過邊的最小值進行差分。

找最小值可以用 \(\rm Floyd\)\(O(n^3)\) 暴力也能過。(關於能不能過可以現場試一下,\(\rm Floyd\) 執行次數比較穩定)。

#include<cstdio>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
inline int read()
{
	int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
template<typename T> inline T max(const T&x,const T&y){return x<y?y:x;}
template<typename T> inline T min(const T&x,const T&y){return x<y?x:y;}
template<typename T> inline void chkmax(T&x,const T&w){x<w?x=w:T();} 
template<typename T> inline void chkmin(T&x,const T&w){w<x?x=w:T();}
const int N=1010,M=2e5+10;int u[M],v[M],G[N][N],n,m,S[M];
int main()
{
	n=read();m=read();rep(1,m,i)u[i]=read(),v[i]=read(),G[u[i]][v[i]]=i;
	drep(n,1,v)
	{
		rep(v+1,n,u)++S[min(G[v][u],G[u][v])];
		rep(1,n,u)if(G[u][v])
		{
			int cur=G[u][v];
			if(u>v)for(int k=1;k<v;++k)chkmax(G[u][k],min(cur,G[v][k]));
			else rep(1,n,k)chkmax(G[u][k],min(cur,G[v][k]));
		}
	}
	drep(m,1,i)S[i]+=S[i+1];rep(1,m+1,i)printf("%d ",n+S[i]);return 0;
}

4.數對

\(B\) 卷的簽到題我當然是不會的啦。

\(a\leq5\times 10^5\) ,由於順序無關,直接 \(O(alna)\) 暴力統計答案就行。

#include<cstdio>
#define rep(a,b,c) for(int c=(a);c<=(b);++c)
inline int read()
{
	int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
const int N=5e5+10;int t[N],mx;
int main()
{
	int n=read();long long ans=0;while(n--){int x=read();mx<x?mx=x:0;++t[x];}
	rep(1,mx,i)if(t[i]){for(int j=i<<1;j<=mx;j+=i)ans+=1ll*t[i]*t[j];ans+=(t[i]-1)*t[i];}
	printf("%lld\n",ans);return 0;
}

5.寶石

寫了個貼著資料範圍的複雜度 \(O(n+c\sqrt{n}+q\sqrt{n}\ logn)\) 的垃圾做法。

首先考慮將 \(P\) 序列下標對映到寶石上(因為 \(P\) 不相同),然後就變成在這個路徑上搞出最多連續的 \(123...x\)

然後路徑直接樹剖搞出來對映到 \(\rm dfs\) 序上,然後問題轉化成當前選到了 \(p\) ,經過一個區間後選到了誰。

分塊常數小,我們要相信奇蹟,直接上分塊 \(O(\sqrt{n}\ logn)\) 查詢就行了(個人喜歡固定塊長,複雜度較玄學)。

然後直接處理每個塊內傳入 \(p\) ,傳出誰,暴力跳就行了,當然正反都要處理一下下。

程式碼很無腦,調的時候出了一個小錯誤,基本算是一遍過:

#include<cstdio>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
#define grep(b,c) for(int c=head[(b)];c;c=nxt[c]) 
inline int read()
{
	int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
template<typename T> inline T min(const T&x,const T&y){return x<y?x:y;}
template<typename T> inline T max(const T&x,const T&y){return x<y?x:y;}
template<typename T> inline void chkmin(T&x,const T&w){w<x?x=w:T();}
template<typename T> inline void chkmax(T&x,const T&w){x<w?x=w:T();}
template<typename T> inline void Swap(T&x,T&y){T tmp=x;x=y;y=tmp;}
const int N=2e5+10,BK=512,C=5e4+10;int head[N],des[N<<1],nxt[N<<1],cgt,loc[N],n,m,c,w[N];
inline void ins(const int &x,const int &y){nxt[++cgt]=head[x];des[head[x]=cgt]=y;nxt[++cgt]=head[y];des[head[y]=cgt]=x;}
namespace Block //nxt --> 傳入將要打入的值,返回經過後將要打入的值 ,inv --> 反轉區間後的nxt 
{
	int nxt[N/BK+10][C],inv[N/BK+10][C],loc[N],ql[N/BK+10],qr[N/BK+10],Blk,vis[C],a[N];
	inline void Init()
	{
		Blk=n/BK;rep(1,Blk,i){ql[i]=qr[i-1]+1;qr[i]=i*BK;rep(ql[i],qr[i],j)loc[j]=i;}
		if(n%BK!=0){ql[Blk+1]=qr[Blk]+1;qr[++Blk]=n;rep(ql[Blk],qr[Blk],j)loc[j]=Blk;}
		rep(1,Blk,B)
		{
			rep(0,c+1,i)nxt[B][i]=inv[B][i]=i;
			rep(ql[B],qr[B],i)inv[B][a[i]]=inv[B][a[i]+1];
			drep(qr[B],ql[B],i)nxt[B][a[i]]=nxt[B][a[i]+1];
		}
	}
	inline void qry(const int &l,const int &r,int &P,const bool &Inv=false)
	{
		if(Inv)
		{
			const int &Bl=loc[l],&Br=loc[r];
			if(Bl==Br){drep(r,l,i)P+=(a[i]==P);return;}
			drep(r,ql[Br],i)P+=(a[i]==P);
			for(int B=Br-1;B>Bl;--B)P=inv[B][P];
			drep(qr[Bl],l,i)P+=(a[i]==P);
		}
		else
		{
			const int &Bl=loc[l],&Br=loc[r];
			if(Bl==Br){ rep(l,r,i)P+=(a[i]==P);return;}
			rep(l,qr[Bl],i)P+=(a[i]==P);
			for(int B=Bl+1;B<Br;++B)P=nxt[B][P];
			rep(ql[Br],r,i)P+=(a[i]==P);
		}
	}
}
namespace TreeCut
{
	using Block::qry;using Block::a;int mxs[N],dep[N],siz[N],top[N],fa[N],idx[N],cdt;
	struct Pair{int x,y;inline Pair(const int &X=0,const int &Y=0){x=X;y=Y;}}stk[55];int Top;
	inline void dfs1(const int &u=1,const int &f=0)
	{
		dep[u]=dep[fa[u]=f]+1;siz[u]=1;int mx=0;
		grep(u,i)if(des[i]!=f){int v=des[i];dfs1(v,u);siz[u]+=siz[v];mx<siz[v]?mx=siz[mxs[u]=v]:0;}
	}
	inline void dfs2(const int &u=1,const int &fa=0,const int &tp=1)
	{
		top[u]=tp;a[idx[u]=++cdt]=w[u];if(mxs[u])dfs2(mxs[u],u,tp);
		grep(u,i)if(des[i]!=mxs[u]&&des[i]!=fa)dfs2(des[i],u,des[i]);
	}
	inline int Qry(int v,int u)
	{
		int P=1;while(top[u]!=top[v])
		{
			if(dep[top[v]]<dep[top[u]])
			{
				qry(idx[top[u]],idx[u],P,true);
				u=fa[top[u]];
			}
			else
			{
				stk[++Top]=Pair(idx[top[v]],idx[v]);
				v=fa[top[v]];
			}
		}
		if(dep[u]<dep[v])qry(idx[u],idx[v],P);
		else qry(idx[v],idx[u],P,true);
		while(Top)qry(stk[Top].x,stk[Top].y,P),--Top;
		return P-1;
	}
}
using TreeCut::Qry;
int main()
{
	n=read();m=read();c=read();rep(1,c,i)loc[read()]=i;
	rep(1,n,i)w[i]=loc[read()];rep(2,n,i)ins(read(),read());
	TreeCut::dfs1();TreeCut::dfs2();Block::Init();int Q=read();
	while(Q--)printf("%d\n",Qry(read(),read()));return 0;
}

6.滾榜

首先嚐試相信奇蹟打一個 \(O(n!\times n)\) 的暴力,然後仍然不會 。可以發現 \(b\) 序列有貪心分法。

不妨令 \(f_i=a_i+b_i\) ,考慮列舉排列 \(P\),先報 \(P_1\) ,然後必須使 \(f_{P_1}>\max \{a\}\) ,於是 \(b_1\) 的值就出來了。

然後接下來就繼續貼著合法條件給出 \(b\) ,最後剩餘的一股腦塞給 \(P_n\) 就行。

考慮把上面式子化一化,發現 \(Sum=\sum (a_{P_{i-1}}-a_{P_i}+(P_i>P_{i-1}))\times(n-i+1)\) ,發現 \(a_i\) 的貢獻可以拆開來算。

然後 \(dp_{S,i,j}\) 表示選了 \(S\) 集合,最後為 \(i\) ,和為 \(j\) 的方案數 \(\rm dp\) 就行。複雜度 \(O(2^nn^2m)\)

#include<cstdio>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
typedef long long LL;
inline int read()
{
	int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
const int N=15,M=510; int n,m,a[N],mx,idx;LL dp[1<<13|15][N][M];int cnt[1<<13|15];
int main()
{
	n=read();m=read();for(int i=0;i<n;++i) a[i]=read(),mx<a[i]?mx=a[idx=i]:0;
	for(int i=0;i<n;++i)
	{
		int tmp=(mx-a[i]+(i>idx))*n;
		if(tmp<=m)dp[1<<i][i][tmp]=1;cnt[1<<i]=1;
	}
	for(int S=0;S<(1<<n);++S)if(!cnt[S])
	{
		cnt[S]=cnt[S^(S&-S)]+1;
		for(int i=0;i<n;++i)if((S>>i)&1)
		{
			int T=S^(1<<i);
			for(int k=0;k<n;++k)if((T>>k)&1)
			{
				int dlt=(a[k]-a[i]+(i>k))*(n-cnt[S]+1);dlt<0?dlt=0:0;
				rep(dlt,m,j)dp[S][i][j]+=dp[T][k][j-dlt];
			}
		}
	}
	LL ans=0;for(int i=0;i<n;++i)rep(0,m,j)ans+=dp[(1<<n)-1][i][j];
	printf("%lld\n",ans);return 0;
}

7.支配

8.取模

玄學題,(其實我不會證複雜度也不想證了QAQ (~懶

考慮 \(O(n^2logn)\) 做法,可以列舉模數,對剩下的數按取模後從大到小排序。不妨設為 \(b\)

最大與次大值加起來有可能是答案,然後繼續用雙指標列舉 \(a_l+a_r<a_i\)\(l,r\)\(\max\)

然後考慮玄學優化:

  1. 兩個以上重複的數沒有任何用處,去個重。

  2. 模數從大到小列舉,如果答案已經大於當前模數就可以停止枚舉了。

正確性顯然,複雜度玄學,據稱是 \(O(nlognlogV)\) 的,可以嘗試手玩一下。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
inline int read()
{
	int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
inline void chkmax(int &x,const int &w){x<w?x=w:0;}
inline bool cmp(const int &x,const int &y){return x>y;}
const int N=2e5+10;int aa[N],n,b[N],ans,c[N],a[N];
int main()
{
	int nn=read();rep(1,nn,i)aa[i]=read();std::sort(aa+1,aa+nn+1,cmp);
	n=2;a[1]=aa[1];a[2]=aa[2];rep(3,nn,i)if(aa[i]!=aa[i-2])a[++n]=aa[i];
	for(int i=1;i<=n&&ans<a[i];++i)
	{
		int B=0,C=0;rep(1,n,j)if(i!=j){int tmp=a[j]%a[i];if(tmp<a[i]/2)c[++C]=tmp;else b[++B]=tmp;}
		std::sort(b+1,b+B+1,cmp);std::sort(c+1,c+C+1,cmp);
		if(B>=2)chkmax(ans,b[1]+b[2]-a[i]);if(C>=2)chkmax(ans,c[1]+c[2]);
		int p=1;drep(B,1,j)
		{
			while(p<=C&&c[p]+b[j]>=a[i])++p;
			if(p==C+1)break;chkmax(ans,c[p]+b[j]);
		}
	}
	printf("%d\n",ans);
}