• Codeforces Global Round 11


    E. Xum

    题目描述

    一开始黑板上写了一个奇数 (x),每次操作可以选取黑板上的两个数,把他们的和或者异或和写在黑板上,试在 (10^5) 次操作内使得黑板上出现 (1),并且要保证任意时刻黑板上的数都不超过 (5cdot 10^{18})

    (3leq xleq 10^6)

    解法

    发现不可能由加法直接得到 (1),得到 (1) 只能通过异或操作,那么求出一组偶数和偶数加一即可。

    因为一开始给出的是奇数 (x),所以偶数可以通过 (kx)(k) 是偶数)得到,那么偶数加一就可以转化成模 (x) 余一。

    这个模 (x) 余一的数肯定是一个数的若干倍,设它是 (z)(p) 倍,那么根据逆元的有关性质只要 (z)(x) 互质那么就一定存在一个合法的 (p),所以问题变成了找一个和 (x) 互质的数,然后解方程 (pz=1mod x) 即可。

    找到这个和 (x) 互质的数肯定要用异或,假设 (x)(1001) 这种形式,那么我们把最低位平移到和最高位对齐,也就是得到 (1001000),把它们异或得到 (1000001),可以证明这个数一定和 (x) 互质,因为 ((x+2^kx-2^k)) 一定和 (x) 互质。

    把上面我讲的东西倒着模拟一遍就可以做出本题。

    #include <cstdio>
    #define int long long
    const int M = 1005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int x,z,m,lx,lz,px[M],pz[M],a[M],b[M],c[M];
    int gcd(int a,int b)
    {
    	return !b?a:gcd(b,a%b);
    }
    int exgcd(int a,int b,int &x,int &y)
    {
    	if(!b) {x=1;y=0;return a;}
    	int g=exgcd(b,a%b,y,x);
    	y-=x*(a/b);
    	return y; 
    }
    signed main()
    {
    	x=z=read();px[0]=x;
    	for(lx=0;px[lx]<=1e18;lx++)
    	{
    		px[lx+1]=px[lx]+px[lx];
    		m++;a[m]=b[m]=px[lx];
    	}
    	while(1)
    	{
    		if(gcd(z^x,x)==1)
    		{
    			m++;a[m]=x;b[m]=z;c[m]=1;
    			z=x^z;break;
    		}
    		m++;a[m]=b[m]=z;z+=z;
    	}
    	pz[0]=z;
    	for(lz=0;pz[lz]<=1e18;lz++)
    	{
    		pz[lz+1]=pz[lz]+pz[lz];
    		m++;a[m]=b[m]=pz[lz];
    	}
    	int k=0,t=0;exgcd(z,x,k,t);
    	k=(k%x+x)%x;
    	if(k%2==0) k+=x;
    	k--;
    	for(int i=lz;i>=0;i--)
    		if(k&(1ll<<i))
    		{
    			m++;a[m]=z;b[m]=pz[i];
    			z+=pz[i];
    		}
    	t=(z-1)/x;t--;
    	for(int i=lx;i>=0;i--)
    		if(t&(1ll<<i))
    		{
    			m++;a[m]=x;b[m]=px[i];
    			x+=px[i];
    		}
    	m++;a[m]=z;b[m]=x;c[m]=1;
    	printf("%lld
    ",m);
    	for(int i=1;i<=m;i++)
    	{
    		if(c[i]==0) printf("%lld + %lld
    ",a[i],b[i]);
    		else printf("%lld ^ %lld
    ",a[i],b[i]);
    	}
    }
    

    F. Boring Card Game

    题目描述

    点此看题

    解法

    简单结构的高级运用,真的让我看到了结构之美。

    首先考虑一个简化版的问题,如果不要求两人顺序操作怎么办(还是不会做),你发现这是一个暴力消去连续三个相同元素的过程,可以用栈解决,也就是向栈顶塞元素,如果同种元素塞满三个那么弹栈顶,最后栈为空。

    你发现栈的作用大概是如果中间出现了连续的三个相同元素,那么把他们消去后,前后的元素还能接起来,再考虑它们是否能被消去。那么此时就存在一个先后顺序的问题,我们把元素每三个一组分组,那么会得到一个形如这样的限制:消去某一组之后才能消去另一组

    考虑用图论来表达这个性质,如果消去 (A) 组才能消去 (B) 组,那么 (A)(B) 连边。考虑在做栈的时候把这个图构建出来,也就是在栈顶出栈的时候,把这个栈顶向新的栈顶连边,我们发现最后得到的结构是一个森林

    再思考题目保证了本题有解,思考有解的必要条件是有一个叶子属于第一个人,那么题目一定保证了这个条件。通过这个条件我们可以推出一定有一个根属于第二个人,可以给出这样的构造方案:

    • 轮到第一个人操作的时候,因为有一个根属于第二个人,所以一定能找到一个叶子,删除它即可。
    • 轮到第二个人操作的时候,由于属于第二个人的点要多一个,所以一定能找到一个叶子,但是注意如果只剩一个根并且不是最后一步就不能删根,只能删其他叶子,要不然第一个人的构造会出问题。

    那么最后一定能删干净,把上述过程模拟一遍就可以得到删边方案。

    总结

    从简化问题入手,首先要能主动提出简化问题,然后找简化问题和原问题之间差了什么,把这点想清楚。

    数据结构的图论模型很重要,这道题就是利用了栈的潜在树结构。

    #include <cstdio>
    const int M = 10005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,a[M],b[M],c[M],d[M];
    int s[M],fa[M],p[M][3],vis[M];
    signed main()
    {
    	n=read();
    	for(int i=1;i<=3*n;i++)
    		a[read()]=1;
    	for(int i=1;i<=6*n;i++)
    	{
    		if(!m || b[s[m]]!=a[i])
    		//insert the stack and create a node
    		{
    			b[++k]=a[i];
    			s[++m]=k;p[k][c[k]++]=i;
    		}
    		else
    		{
    			p[s[m]][c[s[m]]++]=i;
    			if(c[s[m]]==3)//pop the stack
    				fa[s[m]]=s[m-1],m--;
    		}
    	}
    	int c=1,cnt=0;
    	for(int i=1;i<=k;i++) d[fa[i]]++;
    	for(int i=1;i<=k;i++) if(!fa[i] && !b[i]) cnt++;
    	for(int i=1;i<=k;i++)
    	{
    		int x=0;
    		for(int j=1;j<=k;j++)
    			if(!d[j] && b[j]==c && !vis[j])
    			{
    				if(i<k && cnt==1 && c==0 && !fa[j]) continue;
    				//must have at least one root
    				x=j;
    			}
    		d[fa[x]]--;c^=1;vis[x]=1;
    		if(b[x]==0 && !fa[x]) cnt--;
    		printf("%d %d %d
    ",p[x][0],p[x][1],p[x][2]);
    	}
    }
    

    CF1427G One Billion Shades of Grey

    题目描述

    点此看题

    解法

    首先有一个明显的 ( t observation):新填入的数值只可能是边界上的数值,应该可以用调整法证明

    提出简化版问题:考虑边界上只有两种数值 (x)(y),那么可以转化成一个染色问题,不同色的点要记录一次代价。那这不是一个显然的最小割模型么?如果边界上的点是 (x),那么把它连 (S),否则把它连 (T),相邻的点之间连一条流量为 (1) 的无向边。

    然后考虑有多种颜色的原问题,如果要套最小割的话那么考虑相邻两个颜色求一次最小割,对于边界上的点如果权值大于等于 (v_{i+1}) 那么连 (T),如果小于等于 (v_i) 那么连 (S),如果两个相邻点的权值是 (s_x,s_y(x<y)) 的话,那么会被拆分成 (s_y-s_x=(s_y-s_{y-1})+(s_{y-1}-s_{y-2})...(s_{x+1}-s_x)) 被统计。

    但是这里有一个逻辑漏洞,由于每次是独立求最小割的,那么对于两个点可能出现 (s_x<k_1,s_y>k_1)(s_x>k_2,s_y<k_2) 都被局部最优解统计了,但这显然出现了矛盾,非要这么做的话就要证明不会出现这种情况。

    Lemma:对于最小割中任意被划分到了 (T) 的点,在把某个点 (u) 由连 (T) 变为连 (S) 之后,新的最小割中这些点不会被划分到 (S) 中。官方题解中有较为严谨的证明,这里给出我的感性证明:

    考虑把一个集合 (s) 调整由染 (T) 调整成染 (S),那么在不考虑和 (u) 连的边时尚且不优(因为上一次没有这样做),那么考虑了和 (u) 连的边割只会增加,所以最小割中不会出现此类情况。

    那么每次就可以单独做了,但是如果我们暴力跑的话好像会超时欸(...)

    由于流量是 (O(n)) 级别的,那么跑一次的复杂度是 (O(n^3)),因为每次只会把一个点由 (T) 改到 (S),把经过这个点的流暴力撤回是 (O(n^2)) 的,一共需要撤回 (n) 次所以总时间复杂度 (O(n^3)),实现用暴力增广的方法即可。

    总结

    考虑简化问题还是很重要啊(...)

    如果知道一个局部问题的最优解,可以通过构造答案上界的方法来获取全局最优解。

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 100005;
    #define ll long long
    #define pii pair<int,int>
    #define mp make_pair
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,tot,f[M],a[205][205],vis[M],is[M],it[M];
    ll ans,flow;vector<pii> v;
    struct edge
    {
    	int v,c,next;
    }e[2*M];
    int id(int x,int y)
    {
    	return (x-1)*n+y;
    }
    void add(int u,int v)
    {
    	e[++tot]=edge{v,0,f[u]},f[u]=tot;
    	e[++tot]=edge{u,0,f[v]},f[v]=tot;
    }
    int dfs(int u,int fl)
    {
    	if(it[u]==1 && fl==1) return 1;//belong to T
    	if(is[u]==1 && fl==-1) return 1;//belong to S
    	if(vis[u]) return 0;
    	vis[u]=1;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(fl==1 && e[i].c==1) continue;//enlarge 
    		if(fl==-1 && e[i].c!=-1) continue;//roll back
    		if(vis[v]) continue;
    		if(dfs(v,fl))
    		{
    			e[i].c++;e[i^1].c--;
    			return 1;
    		}
    	}
    	return 0;
    }
    signed main()
    {
    	n=read();tot=1;
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			a[i][j]=read();
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    		{
    			if(a[i][j]<0) continue;
    			if(j<n && a[i][j+1]>=0)
    				add(id(i,j),id(i,j+1));
    			if(i<n && a[i+1][j]>=0)
    				add(id(i,j),id(i+1,j));
    			if(a[i][j]>0) it[id(i,j)]=1,
    				v.push_back(mp(a[i][j],id(i,j)));
    		}
    	sort(v.begin(),v.end());
    	for(int i=0;i+1<v.size();i++)
    	{
    		int u=v[i].second;
    		memset(vis,0,sizeof vis);
    		while(dfs(u,-1))
    		{
    			flow--;
    			memset(vis,0,sizeof vis);
    		}
    		it[u]=0;is[u]=1;
    		memset(vis,0,sizeof vis);
    		for(int j=0;j<=i;j++)
    			while(dfs(v[j].second,1))
    			{
    				flow++;
    				memset(vis,0,sizeof vis);
    			}
    		ans+=flow*(v[i+1].first-v[i].first);
    	}
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    Golang---反射(reflect)
    golang--交替打印一个数组中的元素
    Golang---基本类型(interface)
    利用random5 生成 random7
    Golang---基本类型(map)
    Golang---基本类型(slice)
    Golang---基本类型(string)
    二维码扫码登录原理
    Golang---内存逃逸
    关于我
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15165965.html
Copyright © 2020-2023  润新知