zoukankan      html  css  js  c++  java
  • AtCoder Regular Contest 119

    B.Electric Board

    题目描述

    点此看题

    给定长度为 (n) 两个字符串 (S,T),要求通过最少的操作数把 (S) 变成 (T),操作就是对于 (s_l=0and s_{l+1}=...=s_r=1) 或者 (s_l=1and s_{l+1}=...=s_r=0) 可以交换元素 (s_l)(s_r)

    (2leq nleq 500000)

    解法1

    我们可以把所有 (0) 换到应该的位置上,那么 (1) 也就确定了。

    (0) 换过去的代价是路上 (0) 的数量,这就和 (1) 没关系了,那么我们把 (S,T)(0) 都取出来,相邻的配对即可。

    解法2

    我们可以把所以 (1) 换到应该的位置上,那么 (0) 也就是确定了。

    (1) 换过去的代价是路上 (0) 的数量,这和 (0/1) 都有关系,直接匹配是行不通的,( t oneindark) 给出的做法是从左往右扫,如果遇到 (S)(1) 但是 (T) 没有就把他移动到右边第一个 (0),如果 (T)(1) 但是 (S) 没有也把他移动到右边第一个 (0)

    这种做法的正确性有二:一是两个状态都往中间靠拢;二是我们永远在不得不操作的时候操作

    C.ARC Wrecker 2

    题目描述

    点此看题

    (n) 个楼房,第 (i) 个高为 (a_i),相邻的楼房可以同时增加或同时减少,问能够推平(高度全部变成 (0))的区间有多少个。

    (2leq nleq 300000,1leq a_ileq 10^9)

    解法

    一定要有敏锐的观察能力,这道题的结论是:如果奇偶位置高度相同则可以推平

    证明不难,因为无论怎么操作奇偶的差都是不变的,而目标奇偶差值为 (0),初始状态一定能到目标状态。

    然后搞一个特殊的前缀和,奇数位置符号为正,偶数位置符号为负,找权值和为 (0) 的区间即可。

    #include <cstdio>
    #include <map>
    using namespace std;
    const int M = 300005;
    #define int 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,ans,a[M];map<int,int> mp;
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		if(i>1) a[i]+=a[i-2];
    	}
    	mp[0]=mp[a[1]]=1;
    	for(int i=1;i<=n/2;i++)
    	{
    		int x=2*i;
    		ans+=mp[a[x-1]-a[x]];
    		if(x<n) ans+=mp[a[x+1]-a[x]];
    		mp[a[x-1]-a[x]]++;
    		mp[a[x+1]-a[x]]++;
    	}
    	printf("%lld
    ",ans);
    }
    

    D.Grid Repainting 3

    题目描述

    点此看题

    解法

    注意到一开始没有白色格子,所以设被染白的行数量为 (a),列数量为 (b),我们只需要最大化:

    [n imes m-(n-a)(m-b) ]

    然后就要开始观察了,不难发现一个红色格子操作后可能覆盖到其他格子,就不能让所有红色格子充分发挥作用。所以如果我们确定了某一个红色格子的状态,那么可以确定和他在同行同列格子的状态,以此类推,这像是一个牵一发而动全身的过程。

    举个例子,比如我们要操作 ((x,y)) 的行 (x),那么和他同行的格子必须选择漂白列,和他同列的格子必须选择漂白行,因为不能影响 ((x,y)),所以我们把每个点和其同行同列的点连边,那么所形成的连通块就能解决所有它的行和列的染色,除了激发点,如果激发点漂白行那么就少了一列的漂白,如果激发点漂白列那么就少了一行的漂白。

    继续发现激发点的选择是无关紧要的,我们可以通过枚举来确定有多少连通块的激发点要漂白行,有多少连通块的激发点要漂白列,输出方案递归即可。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 2505;
    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,a,b,cnt,ans,p,r[M],c[M];
    char s[M][M];vector<int> v;
    void dfs(int x,int y)
    {
    	if(r[x] && c[y]) return ;
    	bool rx=r[x],cy=c[y];r[x]=c[y]=1;
    	if(!rx) for(int i=1;i<=m;i++)
    		if(s[x][i]=='R') dfs(x,i);
    	if(!cy) for(int i=1;i<=n;i++)
    		if(s[i][y]=='R') dfs(i,y);
    }
    void print(int x,int y)
    {
    	if(r[x] && c[y]) return ;
    	bool rx=r[x],cy=c[y];r[x]=c[y]=1;
    	if(!rx) for(int i=1;i<=m;i++)
    		if(s[x][i]=='R') print(x,i);
    	if(!cy) for(int i=1;i<=n;i++)
    		if(s[i][y]=='R') print(i,y);
    	if(!rx && cy) printf("X %d %d
    ",x,y);
    	if(rx && !cy) printf("Y %d %d
    ",x,y);
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;i++)
    		scanf("%s",s[i]+1);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			if(s[i][j]=='R' && !r[i] && !c[j])
    			{
    				dfs(i,j);cnt++;
    				v.push_back(i*m+j-1);
    			}
    	for(int i=1;i<=n;i++) a+=r[i];
    	for(int i=1;i<=m;i++) b+=c[i];
    	for(int i=0;i<=cnt;i++)
    	{
    		int x=n-a+i,y=m-b+(cnt-i);
    		if(n*m-x*y>ans) ans=n*m-x*y,p=i;
    	}
    	for(int i=1;i<=max(n,m);i++)
    		c[i]=r[i]=0;
    	printf("%d
    ",a+b-cnt);
    	for(int i=0;i<p;i++)
    	{
    		print(v[i]/m,v[i]%m+1);
    		printf("Y %d %d
    ",v[i]/m,v[i]%m+1);
    	}
    	for(int i=p;i<cnt;i++)
    	{
    		print(v[i]/m,v[i]%m+1);
    		printf("X %d %d
    ",v[i]/m,v[i]%m+1);
    	}
    }
    

    E.Pancakes

    题目描述

    点此看题

    解法

    翻转一段区间 ([l,r]),两边和中间相邻数字差都不会变,只有左右端点会变,会令答案加上:

    [|s_r-s_{l-1}|+|s_{l}-s_{r+1}|-(|s_l-s_{l-1}|+|s_r-s_{r+1}|) ]

    后面那一块我们先不管,我们来把前面的绝对值拆开:

    • 如果 (s_rgeq s_{l-1},s_lgeq s_{r+1}),拆开后是 ((s_r-s_{r+1})-(s_l-s_{l-1}))
    • 如果 (s_rgeq s_{l-1},s_lleq s_{r+1}),拆开后是 ((s_r+s_{r+1})-(s_l+s_{l-1}))

    这道题有一个十分关键的点就是不需要区分是 (l) 还是 (r),比如说某一个情况我们算成 (r) 在前 (l) 在后,这对应着另一种 (l) 在前 (r) 在后的情况,所以我们只需要看成 ((s_x,s_{x-1})) 这若干个二元组求偏序关系即可。

    然后偏序关系还有两种,但是会被 ( t Casespace 1,2) 给计算到所以不用管,时间复杂度 (O(nlog n))

    F.AtCoder Express 3

    题目描述

    点此看题

    (n+1) 个按编号顺次的站台,其中 (0)(n) 站台既能坐 (A) 号列车也能坐 (B) 号列车,除 (0)(n) 站台外,其他站台为 (A)(B) 或未知

    乘坐列车花费 (1) 能到达下一个或上一个有此号车的站台,除此之外还可以花费 (1) 到相邻的站台

    现给出每个站台(可能)有的列车,求从 (0)(n) 花费最小值不超过 (k) 的方案数,答案取模 (1e9+7)

    (2leq nleq 4000)

    解法

    考虑用 (dp) 算方案数,我们要把最短路给存在状态里面,(A) 号和 (B) 号列车的最短路要分开存,所以定义状态 (dp[i][x][y]) 表示考虑到了 (i) 个车站,从 (0) 到离 (i) 最近的 (A) 车站最短路是 (x),到离 (i) 最近的 (B) 车站最短路是 (y) 的方案数。

    虽然转移是 (O(1)) 的,但是状态数都达到了 (O(n^3))

    考虑优化状态定义,按道理 (x,y) 的差值不会很大,但是有一种情况立马会让你打消这个念头:(ABBBB......)

    但是只有这一种情况啊!我们解决他不就行了?我们到中间某个 (B) 是很费时的,但是到这一段 (B) 的最后一个 (B) 却是比较快的,因为我们可以直接从 (A) 坐车过去然后走一步就可以到了。

    那么我们用这个来定义状态,设 (f[i][x][y]) 表示到离 (i) 最近的 (A) 车站的最短路是 (x)(i)(B) 车站,到 (i) 以后的这一段 (B) 的最短路是 (x+y) 的方案数(还有一个状态表示 (i)(A) 车站同理,记为 (g)),那么重要的性质是 (-2leq yleq 2)

    现在考虑怎么转移,分为两种情况讨论,设 (d=x+y)

    • 如果是 (A...BB) 这种类型,那么 (f[i+1][x][min(x+2,d+1)-x]leftarrow f[i][x][y]),因为可以从 (i)(B) 走一步到达,或者是 (j+2) 到这一段最后的那个 (B),注意这里可以直接用 (d) 是因为如果到第一个 (B) 的最短路 (>d) 那么 (x+2) 就是小的那个。
    • 如果是 (A...BA) 这种类型,那么 (g[i+1][d][min(x+1,d+1)-d]leftarrow f[i][x][y]),可以直接走路或者坐车一步到。

    那么时间复杂度 (O(n^2))

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std; 
    const int M = 5005;
    const int MOD = 1e9+7;
    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,k,ans,f[M][5][2],g[M][5][2];char s[M];
    void add(int &x,int y) {x=(x+y)%MOD;}
    signed main()
    {
    	n=read();k=read();
    	scanf("%s",s+1);
    	if(s[1]!='A') f[0][1+2][0]=1;
    	if(s[1]!='B') f[0][1+2][1]=1;
    	for(int i=2;i<n;i++)
    	{
    		memset(g,0,sizeof g);
    		for(int j=0;j<=i;j++)
    		{
    			for(int k=-2;k<=2;k++)
    			{
    				int d=j+k;if(d<0) continue;
    				if(f[j][k+2][0])
    				{
    					if(s[i]!='A') add(g[j][min(j+2,d+1)-j+2][0],f[j][k+2][0]);
    					if(s[i]!='B') add(g[d][min(j+1,d+1)-d+2][1],f[j][k+2][0]);
    				}
    				if(f[j][k+2][1])
    				{
    					if(s[i]!='B') add(g[j][min(j+2,d+1)-j+2][1],f[j][k+2][1]);
    					if(s[i]!='A') add(g[d][min(j+1,d+1)-d+2][0],f[j][k+2][1]);
    				}
    			}
    		}
    		memcpy(f,g,sizeof g);
    	}
    	for(int i=0;i<=k;i++)
    		for(int j=-2;j<=2;j++)
    			if(i+1<=k || i+j+1<=k)
    				add(ans,f[i][j+2][0]),add(ans,f[i][j+2][1]);
    	printf("%d
    ",ans);
    }
    
  • 相关阅读:
    c语言 数组名是常量指针
    c语言 动态数组
    c语言 指针的值
    c语言 &取地址运算符的理解
    c语言 指针与地址的区别
    c语言 指针与数组
    linux cheese摄像机工具在window电脑上显示
    C#实现简单的 Ping 的功能,用于测试网络是否已经联通
    c# 扫描局域网IP列表的几种方法
    c# 遍历局域网计算机(电脑)获取IP和计算机名称
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14782655.html
Copyright © 2011-2022 走看看