zoukankan      html  css  js  c++  java
  • Codeforces Round #673 (Div. 1)

    B. Make Them Equal

    题目描述

    点此看题

    解法

    这道题需要发掘题目的特殊性质,你发现移动的石子必须是 (i) 的倍数,那么我们可以把所有石子移动到 (1),然后再分配,这整个过程的操作数不能超过 (3n)

    我们是知道最后每个位置的石子有多少的,如果 (sum a_i\%n ot=0) 就直接无解。

    现在的问题是把所有的石子移动到 (1),需要 (i) 位置上的石子数是 (i) 的倍数才可以全部移动,所以我们可能会先把 (1) 的石子移给他然后再全部拿回来。注意到一个重要条件:(a_igeq 1),并且模数是 (i),所以可以从左往右扫,扫到 (i) 的时候 (1)(i) 石子总和一定是够 (i) 的,所以就可以把所有石子移到 (1),步数消耗 (2n)

    最后消耗 (n) 步自由分配即可。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 300005;
    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 T,n,m,a[M];
    struct node
    {
    	int x,y,z;
    };vector<node> b;
    void work()
    {
    	n=read();m=0;b.clear();
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		m+=a[i];
    	}
    	if(m%n) {puts("-1");return ;}
    	m/=n;
    	for(int i=2;i<=n;i++)
    	{
    		if(a[i]%i)
    			b.push_back(node{1,i,i-a[i]%i});
    		b.push_back(node{i,1,(i+a[i]-1)/i});
    	}
    	for(int i=2;i<=n;i++)
    		b.push_back(node{1,i,m});
    	printf("%d
    ",b.size());
    	for(int i=0;i<b.size();i++)
    		printf("%d %d %d
    ",b[i].x,b[i].y,b[i].z);
    }
    signed main()
    {
    	T=read();
    	while(T--) work();
    }
    

    C. XOR Inverse

    题目描述

    点此看题

    解法

    这道题又是异或又是逆序对的直接把我整傻了,但是不要被吓到,我们考虑两个数什么时候会成为逆序对

    其实就是看是否满足 (a_ioplus x>a_joplus xand i<j),既然是位运算我们用二进制的思维去考虑:最高位决定大小关系。设 (a_i,a_j) 第一个不同的数位是 (t),那么它们一定就会在 (x)(t) 这一位决出胜负,如果 (a_i) 这一位为 (1)(a_j) 这一位为 (0),那么 (x) 这一位为 (0) 的时候一定有逆序对否则一定没有逆序对。

    那么每一对数是否产生逆序对就是在某一位上决定的,对于每一位是独立的,所以可以只要统计出这些数对第一个不同的数位,就可以逐位确定 (x) 了,统计的过程可以用 ( t trie) 树实现,时间复杂度 (O(nlog n))

    #include <cstdio>
    const int M = 300005;
    const int N = 40*M;
    #define ll long long
    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,cnt,rt,jzm,a[M],ls[N],rs[N];ll zxy,s[N],b[31][2];
    void ins(int d,int &x,int v)
    {
    	if(!x) x=++cnt;
    	if(d==0)
    	{
    		s[x]++;
    		return ;
    	}
    	if(v&(1<<(d-1)))
    	{
    		b[d-1][1]+=s[ls[x]];
    		ins(d-1,rs[x],v);
    	}
    	else
    	{
    		b[d-1][0]+=s[rs[x]];
    		ins(d-1,ls[x],v);
    	}
    	s[x]=s[ls[x]]+s[rs[x]];
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    	{
    		int x=read();
    		ins(30,rt,x);
    	}
    	for(int i=30;i>=0;i--)
    	{
    		if(b[i][0]<=b[i][1]) zxy+=b[i][0];
    		else zxy+=b[i][1],jzm+=(1<<i);
    	}
    	printf("%lld %d
    ",zxy,jzm);
    }
    

    E. Split

    题目描述

    点此看题

    解法

    ( t div1) 的神题,让我重新认识了什么叫 (dp) 优化。

    首先设计暴力 (dp),设 (dp[i][j]) 表示考虑 (a_1sim a_i),结尾的 (b_{2i})(j),最大相邻相同的数对有多少个(最后减一下得到答案),这样做复杂度直接上天。这时候普通的竞赛选手(比如我)可能就去想别的状态定义了,好像这东西也优化不动啊。但现在不妨观察一下这个状态转移的特点

    • (dp) 的过程是分层的,我们把 (dp[i]) 这一层称为 (cur)(dp[i+1]) 这一层称为 (nxt),令 (x=a_{i+1})
    • (nxt[i]geq max(cur)),因为总可以找到一种方法从上一层的最大值转移过来。
    • (max(nxt)-min(nxt)leq 2),因为每次转移最多增加两对相邻相同数,这是本题的关键,能简化情况。
    • (max(nxt)-min(nxt)=2) 要求 (a_i) 为偶,并且 (nxt[a_i/2]) 是这层 (dp) 值中唯一的最大值。
    • 对于某些 (i) 我们能找到 (cur[x-i]+1) 转移到 (nxt[i]) 的方法。
    • 如果 (x) 是偶数,与其在转移是分开讨论他,不如在转移结束之后给 (nxt[x/2])(1)
    • 虽然每一层的转移 (dp) 值会有差异,但是经过一次转移之后所有 (dp) 值都会到一个新的基准值

    根据上面的观察,我们可以维护下面的东西来代替 (dp) 过程:

    • (zero) 表示这一层所有 (dp) 值的基准线,也就是这一层的最小值。
    • (one) 表示一个集合维护满足下列条件的下标 (i)(cur[i]=zero+1)
    • (two) 表示这一层值为 (zero+2) 的下标,如果没有则为 (-1),有则为 (a_i/2)

    现在考虑如何维护。第一种情况,上一层存在 (two),那么这一层的基准一定选他了(注意这里是不考虑 (x/2) 多出来的贡献 (1) 才能这么贪心),直接清空上一层维护的 (one),看 (x-two) 能不能加入 (one) 里面。

    第三种情况,(two,one) 都不存在,那么可以把 ([1,min(a_i,x)-1]) 加入 (one) 中((shift=x,sign=-1)),因为这里我们是加入的一整段所以我们选择以区间的形式来维护 (one),看能不能保证复杂度。

    第二种情况,上一层 (one) 非空,那么这一层的基准线一定选他了。然后要考虑这一层的 (one) 是怎么样的,上一层的元素 (A) 到这一层就变成了 (x-A),所以我们要求 (Aleq x),也就是我们要删除一段后缀。但是 (A ightarrow x-A) 这个类似以 (x) 为轴的旋转操作有点难搞,可以考虑懒惰的旋转,手玩一下发现规律是 (A,x-A,y-x+A,z-y+x-A...),可以维护旋转值 (shift)(A) 前面的符号 (sign),每次 (shift=shift-x,sign imes-1),那么每次就要根据 (sign) 的正负判断删除的是前缀还是后缀。

    然后特殊考虑 (x/2) 这一位,首先从 (one) 里面去找他,如果找到了就是 (two),如果找不到就把他加入 (one) 中。

    #include <cstdio>
    #include <iostream>
    #include <set>
    using namespace std;
    const int M = 500005;
    #define make make_pair
    #define int long long
    #define pii pair<int,int>
    #define fi first
    #define se second
    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 T,n,a[M];
    void work()
    {
    	n=read();
    	set<pii> one;set<pii>::iterator it;
    	int ze=0,two=-1,sh=0,f=1,ans=2*n;
    	for(int i=1;i<=n;i++)
    	{
    		int x=read();a[i]=x;
    		if(two!=-1)
    		{
    			one.clear();sh=0;f=1;ze+=2;//based on TWO
    			if(x>two) one.insert(make(x-two,x-two));
    		}
    		else if(!one.empty())
    		{
    			ze++;//based on ONE
    			if(f==-1)
    			{
    				//sh-idx>=x -> idx<=sh-x is illegal
    				int lim=sh-x;
    				while(!one.empty() && one.begin()->se<=lim)
    					one.erase(one.begin());//delete the whole segment
    				if(!one.empty() && one.begin()->fi<=lim)//delete the part of the segment
    				{
    					it=one.begin();int tmp=it->se;
    					one.erase(it);
    					one.insert(make(lim+1,tmp));
    				} 
    			}
    			else
    			{
    				//sh+idx>=x -> idx>=x-sh is illegal
    				int lim=x-sh;
    				while(!one.empty() && one.rbegin()->fi>=lim)
    					one.erase(--one.end());
    				if(!one.empty() && one.rbegin()->se>=lim)
    				{
    					int tmp=one.rbegin()->fi;
    					one.erase(--one.end());
    					one.insert(make(tmp,lim-1));
    				}
    			}
    			sh=x-sh;
    			f*=-1;//we rotate lazily
    		}
    		else//every element is the same
    		{
    			f=-1;sh=x;
    			int lim=min(a[i-1]-1,x-1);
    			if(1<=lim) one.insert(make(1,lim));
    		}
    		//here consider x/2
    		two=-1;
    		if(x%2==0)
    		{
    			int y=x/2,val=(y-sh)/f,pd=0;//we reflect the value in ONE
    			it=one.lower_bound(make(val+1,(int)-1e18));//attention
    			if(it!=one.begin())
    			{
    				it--;
    				if(it->fi<=val && val<=it->se) pd=1;
    			}
    			if(pd) two=x/2;//we find TWO
    			else one.insert(make(val,val));//otherwise add it into ONE
    		}
    	}
    	if(two!=-1) ans-=ze+2;
    	else if(!one.empty()) ans-=ze+1;
    	else ans-=ze;
    	printf("%lld
    ",ans);
    }
    signed main()
    {
    	T=read();
    	while(T--) work();
    }
    

    F. Showing Off

    题目描述

    点此看题

    解法

    由于不能走出边界,我们可以感觉出答案大概是若干个基环内向树,并且环长为偶数。

    有一个重要的 ( t observation):就是我们只选环长为 (2) 最优,更长的环可以拆成环长为 (2) 的。

    然后我们考虑用类似二分图匹配来解决问题,先对原图黑白染色,如果相邻两个元素权值相等就连一条边,表示可以匹配。

    这个点还可以作为基环树链上的点存在,所以可以找到周围比它小的权值,连他是合法的,如果这个点没有匹配可以选择这个方案。但是如果周围没有比它小的权值,说明这个点没有退路只有匹配了,我们可以用上下界网络流把它连向源或汇点的边设置必须选。

    #include <cstdio>
    #include <queue>
    using namespace std;
    const int M = 100005;
    const int inf = 0x3f3f3f3f;
    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 T,n,m,tot,cnt,cur[M],f[M],a[M],val[M],dir[M],dis[M];
    int s,t,ss,tt,dx[4]={0,0,1,-1},dy[4]={-1,1};
    char st[4]={'L','R','D','U'};
    struct edge
    {
    	int v,c,next;
    }e[10*M];
    struct node
    {
    	int a,b,c,d;
    }b[10*M];
    int id(int x,int y)
    {
    	return (x-1)*m+y;
    }
    int in(int x,int y)
    {
    	return 1<=x && x<=n && 1<=y && y<=m;
    }
    void add(int u,int v,int c)
    {
    	e[++tot]=edge{v,c,f[u]},f[u]=tot;
    	e[++tot]=edge{u,0,f[v]},f[v]=tot;
    }
    int bfs()
    {
    	for(int i=1;i<=tt;i++) dis[i]=0;
    	queue<int> q;q.push(s);dis[s]=1;
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();
    		if(u==t) return 1;
    		for(int i=f[u];i;i=e[i].next)
    		{
    			int v=e[i].v;
    			if(!dis[v] && e[i].c>0)
    			{
    				dis[v]=dis[u]+1;
    				q.push(v);
    			}
    		}
    	}
    	return 0;
    }
    int dfs(int u,int ept)
    {
    	if(u==t) return ept;
    	int flow=0,tmp=0;
    	for(int &i=cur[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(dis[v]==dis[u]+1 && e[i].c>0)
    		{
    			tmp=dfs(v,min(ept,e[i].c));
    			if(!tmp) continue;
    			e[i].c-=tmp;
    			e[i^1].c+=tmp;
    			flow+=tmp;
    			ept-=tmp;
    			if(!ept) break;
    		}
    	}
    	return flow;
    }
    int dinic()
    {
    	int res=0;
    	while(bfs())
    	{
    		for(int i=1;i<=tt;i++) cur[i]=f[i];
    		res+=dfs(s,inf);
    	}
    	return res;
    }
    void work()
    {
    	n=read();m=read();tot=1;cnt=0;
    	s=n*m+1;t=s+1;ss=s+2;tt=s+3;
    	for(int i=1;i<=tt;i++) f[i]=tot;
    	for(int i=1;i<=n*m;i++) a[i]=read();
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    		{
    			int fl=0;
    			for(int d=0;d<4;d++)
    			{
    				int x=i+dx[d],y=j+dy[d];
    				if(in(x,y) && (i+j)%2 && a[id(x,y)]==a[id(i,j)])
    				{//add the edge of match
    					add(id(i,j),id(x,y),1);
    					b[tot]=node{d,d^1,id(i,j),id(x,y)};
    				}
    				if(in(x,y) && a[id(x,y)]<a[id(i,j)])
    				{//(x,y) is a point (i,j) can relay on
    					val[id(i,j)]=a[id(i,j)]-a[id(x,y)];
    					dir[id(i,j)]=d;fl=1;
    				}
    			}
    			if(!fl)//(i,j) must match
    			{
    				cnt++;//count the mush-match point
    				if((i+j)%2) add(s,id(i,j),1),add(ss,t,1);
    				else add(id(i,j),t,1),add(s,tt,1);
    			}
    			else//just link a normal edge
    			{
    				if((i+j)%2) add(ss,id(i,j),1);
    				else add(id(i,j),tt,1);
    			}
    		}
    	add(tt,ss,inf);
    	if(dinic()!=cnt) {puts("NO");return ;}
    	puts("YES");
    	for(int i=3;i<=tot;i+=2)
    	{
    		if(e[i].c>0 && b[i].d>0)//the opposite edge flows
    		{
    			dir[b[i].c]=b[i].a;
    			dir[b[i].d]=b[i].b;
    			val[b[i].c]=1;
    			val[b[i].d]=a[b[i].c]-1;
    		}
    		b[i]=node{0,0,0,0};
    	}
    	for(int i=1;i<=n;i++,puts(""))
    		for(int j=1;j<=m;j++)
    			printf("%d ",val[id(i,j)]);
    	for(int i=1;i<=n;i++,puts(""))
    		for(int j=1;j<=m;j++)
    			printf("%c ",st[dir[id(i,j)]]);
    }
    signed main()
    {
    	T=read();
    	while(T--) work();
    }
    
  • 相关阅读:
    Linux find 用法示例
    [转载]进程的概念与结构
    linux vi命令详解
    vi快捷键必知必会
    vim分屏操作
    硬连接和软连接的区别
    javascript_console调试常用方法
    javascript_错误处理机制
    Array详解
    react--setState使用
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14770466.html
Copyright © 2011-2022 走看看