• 【考试总结】20220515


    出题人

    如果 \(\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;
    }
    

  • 相关阅读:
    tomcat与eclipse配置
    关于hyperic5.0安装/使用笔记
    java小程序,模拟电视机和遥控
    seldom潜水
    兼容性调试平台
    潜水pw
    scrcpy 👍
    潜水远程调试设备
    尝试方法 在众多平台快速通过脚本自动化验证页面兼容性,可以验证pc,web ,android ,ios,驱动支持java,ruby,c#,python ,js
    try catch嵌套try catch
  • 原文地址:https://www.cnblogs.com/yspm/p/16276029.html
Copyright © 2020-2023  润新知