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

    Codeforces Round #556 (Div. 1)

    A. Prefix Sum Primes

    给你一堆1,2,你可以任意排序,要求你输出的数列的前缀和中质数个数最大。

    发现只有(2)是偶质数,那么我们先放一个(2),再放一个(1),接下来把(2)全部放掉再把(1)全部放掉就行了。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    int n,a[3];
    int main()
    {
    	n=read();
    	for(int i=1;i<=n;++i)a[read()]+=1;
    	if(a[2])
    	{
    		printf("2 "),a[2]-=1;
    		if(a[1])printf("1 "),a[1]-=1;
    	}
    	while(a[2])printf("2 "),a[2]-=1;
    	while(a[1])printf("1 "),a[1]-=1;
    	puts("");
    	return 0;
    }
    

    B. Three Religions

    给你一个串(S),会动态的修改三个串,修改是在三个串的末尾删去或加入一个字符。
    每次修改完之后回答这三个串能否表示成(S)的三个不交的子序列。

    考虑一个(dp)(f[i][a][b][c]),表示当前考虑到了串的第(i)个位置,匹配了到了三个串的(a,b,c)位置。
    然后发现第一维这个东西非常蠢。把状态改一下,变成(f[a][b][c])表示三个串分别匹配到(a,b,c)(i)的最小值。
    这样子单次修改转移的复杂度就是(O(len^2)),这样子复杂度就很对了。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define MAX 100100
    int n,Q;
    int nxt[MAX][26],lst[26];
    char s[MAX],a[4][MAX];
    int len[4],f[255][255][255];
    void dp(int x,int y,int z)
    {
    	if(!(x|y|z))return;f[x][y][z]=n+1;
    	if(x&&f[x-1][y][z]<=n)f[x][y][z]=min(f[x][y][z],nxt[f[x-1][y][z]][a[1][x]-97]);
    	if(y&&f[x][y-1][z]<=n)f[x][y][z]=min(f[x][y][z],nxt[f[x][y-1][z]][a[2][y]-97]);
    	if(z&&f[x][y][z-1]<=n)f[x][y][z]=min(f[x][y][z],nxt[f[x][y][z-1]][a[3][z]-97]);
    }
    int main()
    {
    	scanf("%d%d%s",&n,&Q,s+1);
    	for(int i=0;i<26;++i)lst[i]=n+1;
    	for(int i=n;~i;--i)
    	{
    		for(int j=0;j<26;++j)nxt[i][j]=lst[j];
    		if(i)lst[s[i]-97]=i;
    	}
    	while(Q--)
    	{
    		char ch[2],ss[2];int x;
    		scanf("%s%d",ch,&x);
    		if(ch[0]=='+')
    		{
    			scanf("%s",ss);
    			a[x][++len[x]]=ss[0];
    			if(x==1)
    				for(int i=0;i<=len[2];++i)
    					for(int j=0;j<=len[3];++j)
    						dp(len[1],i,j);
    			if(x==2)
    				for(int i=0;i<=len[1];++i)
    					for(int j=0;j<=len[3];++j)
    						dp(i,len[2],j);
    			if(x==3)
    				for(int i=0;i<=len[1];++i)
    					for(int j=0;j<=len[2];++j)
    						dp(i,j,len[3]);
    		}
    		else --len[x];
    		if(f[len[1]][len[2]][len[3]]<=n)puts("YES");else puts("NO");
    	}
    	return 0;
    }
    

    C. Tree Generator™

    给你一个括号序列,显然一个合法的括号序列和一棵树是对应的。
    现在每次交换括号序列的两个位置,求交换完之后的每一棵树的直径。

    首先问题可以变成给定把(看成(1),把)看成(-1)
    于是问题就变成了你要在括号序列上找到任意一段连续的子串,并且把它分成两段,使得后一半的值减去前一半的值最大。
    然后线段树维护一下就行了。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define MAX 200200
    #define lson (now<<1)
    #define rson (now<<1|1)
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    int n,Q;char s[MAX];
    struct Node{int pre[2],suf[2],ans,sum,tot;}t[MAX<<2],L,R;
    Node operator+(Node a,Node b)
    {
    	Node c;
    	c.pre[0]=max(a.pre[0],a.sum+b.pre[0]);
    	c.pre[1]=max(a.pre[1],max(a.tot+b.pre[0],-a.sum+b.pre[1]));
    	c.suf[0]=max(b.suf[0],-b.sum+a.suf[0]);
    	c.suf[1]=max(b.suf[1],max(b.tot+a.suf[0],b.sum+a.suf[1]));
    	c.sum=a.sum+b.sum;
    	c.tot=max(a.tot+b.sum,-a.sum+b.tot);
    	c.ans=max(max(a.ans,b.ans),max(a.suf[0]+b.pre[1],a.suf[1]+b.pre[0]));
    	return c;
    }
    void Modify(int now,int l,int r,int p)
    {
    	if(l==r){t[now]=(s[p]=='(')?L:R;return;}
    	int mid=(l+r)>>1;
    	if(p<=mid)Modify(lson,l,mid,p);
    	else Modify(rson,mid+1,r,p);
    	t[now]=t[lson]+t[rson];
    }
    int main()
    {
    	L=(Node){1,1,0,1,1,1,1};R=(Node){0,1,1,1,1,-1,1};
    	n=(read()-1)<<1;Q=read();scanf("%s",s+1);
    	for(int i=1;i<=n;++i)Modify(1,1,n,i);
    	printf("%d
    ",t[1].ans);
    	while(Q--)
    	{
    		int x,y;swap(s[x=read()],s[y=read()]);
    		Modify(1,1,n,x);Modify(1,1,n,y);
    		printf("%d
    ",t[1].ans);
    	}
    	return 0;
    }
    

    D. Abandoning Roads

    你有一张图,你需要对于每一个点回答在任意一棵最小生成树中,(1)号点到这个点的路径的最小值是多少。
    边权只有两种。

    首先小的边权叫做(a),大的边权叫做(b)
    现在是(a)边构成了一堆联通块,然后你用(b)边把这些联通块链接然后求最短路。
    这里直接求最短路有一个问题就是你不能在同一个联通块内用(b)边,也不能让一条路径重复的经过两次同一个联通块。
    那么我们考虑状压,记录已经访问过了哪一些联通块。
    然而这样子是(2^n)的。
    发现大小为(1,2,3)的联通块你不可能用(b)边绕一圈再重新进来,所以只需要考虑大小至少为(4)的联通块。这样子复杂度就降下来了

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    #define MAX 72
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    struct Line{int v,next,w;}e[500];
    int h[MAX],cnt=1;
    inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
    int f[MAX],sz[MAX];
    int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);}
    void Merge(int x,int y){x=getf(x);y=getf(y);f[y]=x;sz[x]+=sz[y];}
    int dis[MAX][131072];bool vis[MAX][131072];
    struct Node{int u,d,S;};
    bool operator<(Node a,Node b){return a.d>b.d;}
    priority_queue<Node> Q;
    void upd(int u,int d,int S){if(dis[u][S]>d)dis[u][S]=d,Q.push((Node){u,d,S});}
    int n,m,A,B,book[MAX],tim;
    int main()
    {
    	n=read();m=read();A=read();B=read();
    	for(int i=1;i<=n;++i)f[i]=i,sz[i]=1,book[i]=-1;
    	for(int i=1;i<=m;++i)
    	{
    		int u=read(),v=read(),w=read();
    		Add(u,v,w);Add(v,u,w);
    		if(w==A)Merge(u,v);
    	}
    	for(int i=1;i<=n;++i)
    		if(book[i]==-1&&sz[getf(i)]>3)
    		{
    			for(int j=i;j<=n;++j)
    				if(getf(i)==getf(j))
    					book[j]=tim;
    			++tim;
    		}
    	memset(dis,63,sizeof(dis));
    	upd(1,0,(~book[1])?1<<book[1]:0);
    	while(!Q.empty())
    	{
    		int u=Q.top().u,S=Q.top().S;Q.pop();
    		if(vis[u][S])continue;vis[u][S]=true;
    		for(int i=h[u];i;i=e[i].next)
    		{
    			int v=e[i].v,w=e[i].w;
    			if(w==A)upd(v,dis[u][S]+w,S);
    			else
    			{
    				if(getf(u)==getf(v))continue;
    				if(~book[v]&&(S&(1<<book[v])))continue;
    				upd(v,dis[u][S]+w,S|((~book[v])?1<<book[v]:0));
    			}
    		}
    	}
    	for(int i=1;i<=n;++i)
    	{
    		int ans=1<<30;
    		for(int j=0;j<1<<tim;++j)ans=min(ans,dis[i][j]);
    		printf("%d ",ans);
    	}
    	return 0;	
    }
    

    E. Election Promises

    有一个(DAG),现在两个人轮流操作。每次操作可以随意指定一个权值不为(0)的点,然后把它的权值减小为任意非负整数,同时可以把其所有出边的点权任意修改。不能操作者输。
    求先手是否必胜。

    首先盲猜肯定和(sg)函数那套理论相关,然后把每个点的(sg)值给求出来。
    题目的结论是:对于所有(sg=i)的点,我们令(s_k=oplus_{sg[i]=k}h[i]),如果存在一个(s_k eq 0)的话那么先手必胜,否则先手必败。

    证明(伪证)
    首先(h)全是(0)的时候一定是先手必败。
    对于一个(sg=k)的点(u),其儿子中必定包含了([0,k-1])这些(sg)值。那么我们找到最大的(sg)值,其一定只能影响所有比他小的(sg)值的位置。
    那么我们修改这个(sg)值中(h)最大的那个点,那么必定可以让它的(sg)值减小,然后一定可以把所有的(s)全部变成(0),于是我们变成了一个必败态。
    而后手此时操作完之后又一定存在至少一个数是(0),又成了必胜态。

    #include<iostream>
    #include<cstdio>
    #include<vector>
    using namespace std;
    #define MAX 200200
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    vector<int> E[MAX];
    int n,m,h[MAX],sg[MAX],sum[MAX];
    int dg[MAX],Q[MAX],vis[MAX];
    void Topsort()
    {
    	int h=1,t=0;
    	for(int i=1;i<=n;++i)if(!dg[i])Q[++t]=i;
    	while(h<=t){int u=Q[h++];for(int v:E[u])if(!--dg[v])Q[++t]=v;}
    }
    int main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;++i)h[i]=read();
    	for(int i=1;i<=m;++i)
    	{
    		int u=read(),v=read();
    		E[u].push_back(v);
    		++dg[v];
    	}
    	Topsort();
    	for(int i=n;i;--i)
    	{
    		int u=Q[i];
    		for(int v:E[u])vis[sg[v]]=i;
    		while(vis[sg[u]]==i)++sg[u];
    		sum[sg[u]]^=h[u];
    	}
    	for(int i=n;~i;--i)
    		if(sum[i])
    		{
    			int pos;
    			for(int j=1;j<=n;++j)
    				if(sg[j]==i&&h[j]>(sum[i]^h[j]))pos=j;
    			h[pos]^=sum[i];
    			for(int v:E[pos])h[v]^=sum[sg[v]],sum[sg[v]]=0;
    			puts("WIN");
    			for(int j=1;j<=n;++j)printf("%d ",h[j]);
    			puts("");return 0;
    		}
    	puts("LOSE");
    	return 0;
    }
    
  • 相关阅读:
    linux 文件记录锁详解
    Linux fcntl函数详解
    大数相加
    信雅达面试题atoi函数实现
    linux getopt函数详解
    strcpy和memcpy的区别
    手把手写数据结构之栈操作
    手把手写数据结构之队列操作
    手把手写数据结构之双向链表操作
    ORACLE查询内存溢出
  • 原文地址:https://www.cnblogs.com/cjyyb/p/10802497.html
Copyright © 2011-2022 走看看