1. 程式人生 > 其它 >【考試總結】2022-05-15

【考試總結】2022-05-15

出題人

如果 \(\exists a_i\equiv0\bmod 2\) 那麼可以讓 \(b_i=\frac{a_i}2\),剩下的 \(b_j=a_j-b_i\)

其實問題就是找到一個 \(\{a\}\) 的子集 \(S\) 使得存在 \(b\) 滿足 \(b_1+b_2=S_1,b_2+b_3=S_2,\dots b_{|S|}+b_1=S_{|S|}\)

這時候 \(S\) 注意這裡的等式一定是全集構成一個整環而不存在枝杈,否則讓基環森林裡面的任意一個環滿足即可

繼續觀察上面的等式發現:由於 \(S_i\) 全是奇數所以 \(|S|\) 是偶數,將 \(S_i\) 按照下標奇偶性分成兩組,那麼兩組的和均為 \(\sum b\)

那麼現在問題就是在 \(\{a\}\) 中找到兩個權值和大小都相同的子集,通過列舉子集的方式可以規避三進位制,使用雜湊表儲存資訊即可

卡常技巧包括但不限於將使用雜湊表的一側大小設為 \(\frac{n}2-2\) 或者當程式執行時間超過閥值輸出 NO

Code Display
const int N=400;
int n,a[N],b[N],v[N];
pair<int,int> ans[N];
unordered_map<int,pair<int,int> > mp[N];
int sum1[1<<15],sum2[1<<17];
signed main(){
	freopen("problemsetter.in","r",stdin); freopen("problemsetter.out","w",stdout);
	n=read();
	rep(i,1,n) a[i]=read();
	rep(i,1,n) if(a[i]%2==0){
		puts("Yes");
		vector<int> b={a[i]/2};
		vector<pair<int,int> > ans;
		rep(j,1,n){
			if(i==j) ans.emplace_back(1,1);
			else{
				b.emplace_back(a[j]-b[0]);
				ans.emplace_back(1,b.size());
			}
		}
		rep(e,0,n-1) print(b[e]); putchar('\n');
		rep(e,0,n-1) print(ans[e].fir),print(ans[e].sec),putchar('\n');
		exit(0);
	}
	if(n<=2) puts("No"),exit(0);
	int n1=max(1ll,n/2-2),n2=n-n1;
	int U1=1<<n1,U2=1<<n2;
	rep(s,1,U1-1){
		int lb=s&(-s),id=__builtin_ctzll(lb)+1;
		sum1[s]=sum1[s^lb]+a[id];
	}
	rep(i,n1+1,n) b[i-n1]=a[i];
	rep(s,1,U2-1){
		int lb=s&(-s),id=__builtin_ctzll(lb)+1;
		sum2[s]=sum2[s^lb]+b[id];
	}
	auto output=[&](const pair<int,int> S,const pair<int,int> T){
		if(S.fir+S.sec+T.sec+T.fir==0) return ;
		vector<pair<int,int> >Plu,Min;
		for(int i=1;i<=n1;++i){
			if(S.fir>>(i-1)&1) Plu.emplace_back(a[i],i);
			if(S.sec>>(i-1)&1) Min.emplace_back(a[i],i);
		}
		for(int i=1;i<=n2;++i){
			if(T.fir>>(i-1)&1) Plu.emplace_back(b[i],i+n1);
			if(T.sec>>(i-1)&1) Min.emplace_back(b[i],i+n1);
		}
		sort(Plu.begin(),Plu.end());
		sort(Min.begin(),Min.end());
		int num=0,cur=0;
		b[++num]=cur;
		int siz=Plu.size();
		while(Min.size()||Plu.size()){
			if(Plu.size()==Min.size()){
				cur=Plu.back().fir-cur;
				ans[Plu.back().sec]={num,num+1};
				Plu.pop_back();
			}else{
				cur=Min.back().fir-cur;
				ans[Min.back().sec]={num,num==siz*2?1:num+1};
				Min.pop_back();
			}
			if(num<siz*2) b[++num]=cur;
		}
		for(int i=1;i<=n;++i) if(!ans[i].fir){
			ans[i]={1,++num};
			b[num]=a[i]-b[1];
		}
		puts("Yes");
		for(int i=1;i<=n;++i) print(b[i]); putchar('\n');
		for(int i=1;i<=n;++i) print(ans[i].fir),print(ans[i].sec),putchar('\n');
		exit(0);
	};

	rep(S,0,U1-1){
		for(int T=S;;T=(T-1)&S){
			int num=__builtin_popcount(T)-__builtin_popcount(S^T);
			mp[n+num][sum1[T]-sum1[S^T]]=make_pair(T,S^T);
			if(!T) break;
		}
	}
	rep(S,0,U2-1){
		for(int T=S;;T=(T-1)&S){
			int num=__builtin_popcount(T)-__builtin_popcount(S^T);
			if(mp[n-num].count(-sum2[T]+sum2[S^T])){
				output(mp[n-num][-sum2[T]+sum2[S^T]],make_pair(T,S^T));	
			}
			if(!T) break;

		}
		if(1.0*clock()/CLOCKS_PER_SEC>1.6) break;
	}
	puts("No");
	return 0;
}

彩色掛飾

建立圓方樹,設 \(f_{x,c}\) 表示 \(x\)\(x\) 表示點雙的根的顏色為 \(c\) 時最少的聯通塊數

對於實點 \(x\) 可以直接合並下轄虛點的資訊即 \(\sum_{v} f_{v,c}\)

對於虛點 \(x\) 需要對聯通分量裡面的異色聯通塊數進行計算:

預處理出來點雙裡面每個子集的匯出子圖形成的聯通塊數量,以及每個子集填入哪種合法的顏色形成的聯通塊最少,再進行一個列舉子集再列舉子集填哪種顏色的 \(\rm DP\) 來計算貢獻

常見的建立圓方樹的過程就能讓父親作為方點出邊 vector 中的第一個點,要注意新增子集的過程中強制讓包含 \(1\) 元素的子集填成所需顏色

注意這樣的統計方式每個方點的父親會被算兩次,但是根這個實點只會被算一次,需要根據實際含義再加加減減

Code Display
const int N=2e5+10,inf=2e5+10;
int n,m,k,s;
vector<int> G[N],g[N];
int dp[N][25];
int col[N][64],con[N][64];
int dfn[N],low[N],stk[N],top,nds,tim;
int fa[N],ini[N];
unordered_set<int> edge[N];
bool exi[10][10];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void tarjan(int x){
	dfn[x]=low[x]=++tim; stk[++top]=x;
	for(auto t:g[x]){
		if(!dfn[t]){
			tarjan(t); ckmin(low[x],low[t]);
			if(low[t]>=dfn[x]){
				++nds;
				G[nds].emplace_back(x);
				G[x].emplace_back(nds);
				do{
					G[nds].emplace_back(stk[top]);
					G[stk[top]].emplace_back(nds);
					--top;
				}while(stk[top+1]!=t);
				int siz=G[nds].size(),U=1<<siz;
				for(int i=0;i<siz;++i){
					for(int j=i+1;j<siz;++j){
						exi[i][j]=exi[j][i]=edge[G[nds][i]].find(G[nds][j])!=edge[G[nds][i]].end();
					}
				}
				for(int i=0;i<U;++i){
					for(int j=0;j<siz;++j) if(i>>j&1){
						int t=G[nds][j];
						fa[j]=j;
						if(ini[t]){
							if(!col[nds][i]) col[nds][i]=ini[t];
							else if(col[nds][i]!=ini[t]) col[nds][i]=-1;		
						}
					}
					for(int j=0;j<siz;++j) if(i>>j&1){
						for(int k=j+1;k<siz;++k) if(i>>k&1){
							if(exi[j][k]) fa[find(j)]=find(k);
						}
					}
					for(int j=0;j<siz;++j) if(i>>j&1) con[nds][i]+=fa[j]==j;
				}
			}
		}else ckmin(low[x],dfn[t]);
	}
	return ;
}
inline int solve(int x,int fat,int c){
	if(~dp[x][c]) return dp[x][c];
	if(x<=n){
		int sum=0;
		for(auto t:G[x]) if(t!=fat) sum+=solve(t,x,c);
		return dp[x][c]=sum;
	}
	if(ini[fat]&&ini[fat]!=c) return dp[x][c]=inf;	
	int num=G[x].size(),U=1<<num; --U;
	int dp1[64],dp2[64][25];
	rep(i,0,k) dp2[0][i]=0;
	for(int i=1;i<=U;++i){
		dp2[i][0]=inf;
		int lb=i&(-i),id=__builtin_ctzll(lb);
		for(int c=1;c<=k;++c){
			if(G[x][id]==fat) dp2[i][c]=dp2[i^lb][c];
			else dp2[i][c]=dp2[i^lb][c]+solve(G[x][id],x,c);
			ckmin(dp2[i][0],dp2[i][c]);
		}
	}
	dp1[0]=0;
	for(int S=1;S<=U;++S){
		dp1[S]=inf;
		for(int T=S;T;T=(T-1)&S){
			if(col[x][T]==-1) continue;
			int cur=col[x][T];
			if(T&1){
				if(cur!=c&&cur) continue;
				cur=c;
			}
			ckmin(dp1[S],dp1[S^T]+con[x][T]+dp2[T][cur]);
		}
	}
	return dp[x][c]=dp1[U];
}
signed main(){
	freopen("colorful.in","r",stdin); freopen("colorful.out","w",stdout);
	nds=n=read(); m=read(); k=read(); read();
	for(int i=1;i<=n;++i) ini[i]=read();
	for(int i=1;i<=m;++i){
		int u=read(),v=read();
		edge[u].insert(v);
		edge[v].insert(u);
		g[u].emplace_back(v);
		g[v].emplace_back(u);
	}	
	for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
	memset(dp,-1,sizeof(dp));
	int val=inf;
	if(ini[1]) val=solve(1,0,ini[1]);
	else rep(i,1,k) ckmin(val,solve(1,0,i));
	print(val-(nds-n)+1);
	return 0;
}

逆轉函式

預處理每個 \(i\) 的上一次下一次的出現位置 \(pre_i,nxt_i\),那麼 \([l,r]\) 存在逆轉函式等價於 \(r-l\le 1\) 或者以下三個條件都滿足:

  • \([l+1,r-1]\) 存在逆轉函式

  • \(pre_r\le l\) 或者 \(a_l=a_{l+r-pre_r}\)

  • \(nxt_l\ge r\) 或者 \(a_r=a_{l+r-nxt_r}\)

注意到這種判定方式和迴文串類似,而所求是每個 “類迴文區間” 的 \(m^{m-n(l,r)}\) 的和,用 \(\rm Manacher\) 來做,計算每個點作為迴文中心時對答案的貢獻

\([l,r]\) 擴充套件成 \([l-1,r+1]\) 時維護 \(n(l,r)\) 以及新增的對答案的貢獻是平凡的,但是在繼承時存在兩者取 \(\min\) 的操作會讓資訊減少,那麼先繼承 \(2i-mid\) 的資訊,再減至 mid+r[mid] 處,資訊的減少和增加的處理方式一樣

觀察到如果保留最靠右的 \(i+r_i\)\(i\)\(i-r_i\) 是不斷右移的,而擴充套件本身會讓最靠右的迴文串的右端點右移,那麼複雜度就是 \(\Theta(n)\)

Code Display
const int N=6e5+10;
int a[N],pw[N],n,m;
int pre[N],lst[N],nxt[N];
int num[N],sum[N],r[N];
signed main(){
	freopen("invfunc.in","r",stdin); freopen("invfunc.out","w",stdout);
	n=read(); m=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=n;i>=1;--i) a[i<<1]=a[i];
	rep(i,1,n*2+1) if(i&1) a[i]=m+1;
	pw[0]=1;
	for(int i=1;i<=n*2+1;++i){
		pw[i]=mul(pw[i-1],m);
		pre[i]=lst[a[i]];
		lst[a[i]]=i;
	}
	for(int i=1;i<=m+1;++i) lst[i]=n*2+2;
	for(int i=n*2+1;i>=1;--i){
		nxt[i]=lst[a[i]];
		lst[a[i]]=i;
	}
	auto check=[&](int l,int r){
		if(r-l<=1) return true;
		if(!l||r>2*n+1) return false;
		if(pre[r]>l&&a[l]!=a[l+r-pre[r]]) return false;
		if(nxt[l]<r&&a[r]!=a[l+r-nxt[l]]) return false;
		return true;
	};
	int ans=0,cen=num[1]=1;
	for(int i=2;i<=n*2;++i){
		if(i<=cen+r[cen]){
			r[i]=r[2*cen-i];
			sum[i]=sum[2*cen-i];
			num[i]=num[2*cen-i];
			while(i+r[i]>cen+r[cen]){
				if((i+r[i])&1) ckdel(sum[i],pw[m+1-num[i]]);
				int lef=cen*2-i-r[i],rig=cen*2-i+r[i];
				if(nxt[lef]>rig) --num[i];
				if(pre[rig]<lef+1) --num[i];
				--r[i];
			}
		}else num[i]=1;
		while(check(i-r[i]-1,i+r[i]+1)){
			++r[i];
			if(nxt[i-r[i]]>i+r[i]-1) ++num[i];
			if(pre[i+r[i]]<i-r[i]) ++num[i];
			if((i+r[i])&1) ckadd(sum[i],pw[m+1-num[i]]);
		}
		if(i+r[i]>=cen+r[cen]) cen=i;
		ckadd(ans,sum[i]);
	}
	print(ans);
	return 0;
}