zoukankan      html  css  js  c++  java
  • Codeforces Round #699 (Div. 2) A~E 题解

    本场链接:Codeforces Round #699 (Div. 2)

    A. Space Navigation

    题目大意:有一个飞船一开始在((0,0)),有一个操作列表(s),每个位置表示一个上下左右的移动操作.有一个目的地坐标是((px,py)).但是这个操作序列不一定能够正确的走到目的地,现在问是否能通过删去某些操作的方式使得飞船能走到目的地.只需输出是否.

    思路

    删除任意元素且不关注删除的数量和具体方案.那么只需考虑是否能通过单一的操作走到最后的目的地就可以了.

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define forn(i,x,n) for(int i = x;i <= n;++i)	
    
    const int N = 1e5+7;
    char s[N];
    
    int main() 
    {
    	int T;scanf("%d",&T);
    	while(T--)
    	{
    		int px,py;scanf("%d%d",&px,&py);
    		scanf("%s",s + 1);int n = strlen(s + 1);
    		vector<int> cnt(4,0);
    		forn(i,1,n)
    		{
    			if(s[i] == 'U')	++cnt[0];
    			else if(s[i] == 'D')	++cnt[1];
    			else if(s[i] == 'L')	++cnt[2];
    			else ++cnt[3];
    		}
    		
    		bool ok = 1;
    		if(px >= 0 && px > cnt[3])	ok = 0;
    		if(px < 0 && -px > cnt[2])	ok = 0;
    		if(py >= 0 && py > cnt[0])	ok = 0;
    		if(py < 0 && -py > cnt[1])	ok = 0;
    		if(!ok)	puts("NO");
    		else puts("YES");
    	}
    	return 0;
    }
    

    B. New Colony

    题目大意:有(n)个山,每个山有高度(h_i).人站在第一个山头上丢牛逼土,牛逼土有自动检查当前高度的牛逼功能,具体来说:

    • 如果当前的位置是(i),那么如果(h_i geq h_{i+1})那么牛逼土会直接飞到下一个山头.
    • 反之牛逼土会觉得当前这个山头不够牛逼,并且牺牲自己让当前的山头高度增加一,即(h_i+=1).
    • 特殊情况:牛逼土可能会飞出(n)个山头之外.

    一共有(k)个牛逼土,每次都在(1)这个位置丢,

    思路

    这个题如果上手模拟一下就会发现情况其实很复杂,因为当前的山头即使走到了下一个山头,那么下一个山头如果还是需要填充高度的话,则仍然会重新走到第一个山头,也就是说牛逼土落到山头的位置是不单调的,虽然有规律但是也很复杂.

    但是这就启发了一个事情:这个题找规律是死路一条,结合数据范围不难猜到极大的情况也不会太多,所以直接模拟冲一发.当然最后就过了.

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define forn(i,x,n) for(int i = x;i <= n;++i)	
    
    const int N = 1e6+7;
    int ans[N],h[105];
    
    int main() 
    {
    	int T;scanf("%d",&T);
    	while(T--)
    	{
    		int n,k;scanf("%d%d",&n,&k);
    		forn(i,1,n)	scanf("%d",&h[i]);
    		h[n + 1] = 0;
    		int last = 1;
    		bool ok = 0;
    		while(1)
    		{
    			forn(i,1,n)
    			{
    				if(i == n)	{ans[last++] = -1;ok = 1;}
    				if(h[i] >= h[i + 1])	continue;
    				++h[i];
    				ans[last++] = i;
    				if(last > k)	ok = 1;
    				break;
    			}
    			if(ok)	break;
    		}
    		if(k < last && ans[k])	printf("%d
    ",ans[k]);
    		else puts("-1");
    	}
    	return 0;
    }
    

    C. Fence Painting

    题目大意:有(n)个栅栏,每个栅栏一开始有一个颜色(a_i),但是你觉得这个栅栏不够牛逼,你想每个栅栏最后的颜色是(b_i)才是牛逼的.但是你也不会涂栅栏,所以现在有(m)个粉刷匠,每个人顺次来,每个人能涂的颜色是(c_i),并且每个人由于提前给过钱了,所以每个人来的时候必须要涂一个栅栏,不能不涂.现在问是否在所有人涂完了之后,能让每个栅栏的颜色达到目标,如果是的话,则输出一个构造的方案,否则输出不能.

    思路

    这个题的模型可以抽象一下:就是有若干个操作顺次执行,每个操作类似于覆盖区间/单点的值,问你最后能不能让整个序列达到什么目标.这个模型有一个很独立的特点:正向的考虑每个覆盖操作会比较麻烦,因为你当前不知道后面的覆盖操作会有什么误差,而且也不可能直接去遍历所有操作看看能不能让以后的操作给你开个后门,白话一下就是你必须大概是个线性(或者带个(log))的做法,但是如果顺次考虑就很困难.不过反过来考虑就很轻松.

    反过来做所有人,那么假如说当前某个人可以画到一个合适的位置,之后往前如果某个人找不到一个合理的位置去涂色,那么可以直接让这个人画到相对于位置之后的那个人的位置,这步策略的意思就是如果一个人没有一个合理的位置去填色的话,那么就填到以后一个会填好的位置,因为涂色的顺序是从前往后的,顺次来看,让这个人涂色的位置会被更后面的一个人覆盖上正确的颜色,那么就不会对正确性产生影响了.

    策略确定好了之后可以考虑怎么构造了:首先看一下所有的栅栏的颜色是否是对应的,假设如果不是对应的,那么对于(b_i)这个颜色,必须要有一个能画(b_i)这个颜色的人去画(i)这个位置,所以开一个邻接表储存所有不匹配的(b_i)需要画在的位置的编号.反过来如果确实是对应的,那么对应的有个好处:(b_i)颜色的人如果过来处理发现没有一个需要他的位置可以直接画在一个本来就是这个颜色的位置,不产生影响,这样的也是合法的.

    最后处理所有人,并且对(a_i)真实的修改,假如某个人的颜色已经没有位置给他填补了,而且也不存在一个本来就是这个颜色的位置,就直接输出无解,反之就有哪填哪.记录下对应的位置最后输出就可以了.

    这个题细节还是很多的,还是建议自己把整个题做出来,完整的考虑清楚每一步.

    代码

    #define _CRT_SECURE_NO_WARNINGS
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define forn(i,x,n) for(int i = x;i <= n;++i)	
    #define forr(i,x,n)	for(int i = n;i >= x;--i)
    
    const int N = 1e5+7;
    int a[N],b[N],c[N],ans[N];
    
    int main() 
    {
    	int T;scanf("%d",&T);
    	while(T--)
    	{
    		int n,m;scanf("%d%d",&n,&m);
    		forn(i,1,m)	ans[i] = 0;
    		forn(i,1,n)	scanf("%d",&a[i]);
    		forn(i,1,n)	scanf("%d",&b[i]);
    		forn(i,1,m)	scanf("%d",&c[i]);
    		
    		map<int,vector<int>> need;
    		map<int,int> backup;
    		forn(i,1,n)
    		{
    			if(a[i] == b[i])
    			{
    				backup[b[i]] = i;
    				continue;
    			}
    			if(!need.count(b[i]))	need[b[i]] = vector<int>();
    			need[b[i]].push_back(i);
    		}
    		
    		vector<int> bad;
    		int last_ok = -1,ok = 1;
    		
    		forr(i,1,m)
    		{
    			if(!need.count(c[i]))
    			{
    				if(last_ok == -1 && !backup.count(c[i]))
    				{
    					ok = 0;
    					break;
    				}
    				if(last_ok != -1)	ans[i] = ans[last_ok],a[ans[i]] = a[ans[last_ok]];
    				else 
    				{
    					ans[i] = backup[c[i]];
    					last_ok = i;
    				}
    				continue;
    			}
    			ans[i] = need[c[i]].back();
    			a[ans[i]] = c[i];
    			need[c[i]].pop_back();
    			if(need[c[i]].empty())	need.erase(c[i]);
    			if(last_ok == -1)	last_ok = i;
    		}
    		forn(i,1,n)	if(a[i] != b[i])	ok = 0;
    		
    		if(!ok)	puts("NO");
    		else
    		{
    			puts("YES");
    			forn(i,1,m)	printf("%d ",ans[i]);puts("");
    		}
    	}
    	return 0;
    }
    

    D. AB Graph

    题目大意:给定一个(n)点的完全图,每个边有一个字母ab.注意:两个点之间的两条边上的字母是不一定相同的.另外给定一个数字(m)要求你构造一个长为(m)的路径,并且这条路径上(m)条边上的字母连起来需要是一个回文串.

    思路

    来讨论一波就可以了,没那么复杂.

    首先可以很容易注意到的一个特殊情况就是如果两个点之间两条边上的字母是相同的,那么可以构造任意情况,来回跑就可以了.

    那么接下来,删掉所有边相同的情况,剩下的只有两个点之间边元素不相同的.对于这种情况通过模拟可以找到:只用这样的两个点,可以构造出所有(m)是奇数的情况.方式就是绕圈走就可以了.

    剩下还有最后一种:所有两个点之间边都是不同的,并且(m)还是个偶数.由于这个题目数据范围比较大,可以猜一下就是正确的构造方式,它包含的点数肯定不多,然后可以找一下三个点之间的关系,可以发现是只有这样的三元组:((u,v,z))之间存在路径(u->v->z)且两个边的元素相同,这时可以构造所有(m)是偶数的情况,如果不存在的话就无解.然后继续讨论可以发现两种情况:如果(m)是四的倍数,这样的情况可以从(v)出发先往(z)绕一圈再往(u)绕一圈回(v).反之就从(u)出发走到(z)再绕回(u).两种情况分别去构造就可以了.

    这题也是细节多,需要考虑清楚输出的内容.

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define forn(i,x,n) for(int i = x;i <= n;++i)	
    typedef pair<int,int> pii;
    #define x first
    #define y second
    
    const int N = 1005;
    char s[N][N];
    int have[N][2];
    
    int main() 
    {
    	int T;scanf("%d",&T);
    	while(T--)
    	{
    		int n,m;scanf("%d%d",&n,&m);
    		forn(i,1,n)	scanf("%s",s[i] + 1),have[i][0] = have[i][1] = -1;
    		
    		bool same = 0;pii res = {-1,-1};
    		forn(u,1,n)	
    		{
    			forn(v,1,n)
    			{
    				if(u == v)	continue;
    				if(s[u][v] == s[v][u])
    				{
    					same = 1;
    					res.x = u,res.y = v;
    					break;
    				}
    			}
    			if(same)	break;
    		}
    		
    		if(same)
    		{
    			puts("YES");
    			for(int i = 1;i <= m + 1;i ++)
    			{
    				if(i % 2 == 1)	printf("%d ",res.x);
    				else printf("%d ",res.y);
    			}
    			puts("");
    			continue;
    		}
    		
    		if(m & 1)
    		{
    			puts("YES");
    			for(int i = 1;i <= m;i += 2)
    			{
    				printf("1 2 ");
    			}
    			puts("");
    			continue;
    		}
    		
    		bool ok = 0;
    		forn(u,1,n)	forn(v,1,n)
    		{
    			if(u == v)	continue;
    			have[u][s[u][v] - 'a'] = v;
    		}
    		
    		forn(u,1,n)
    		{
    			forn(v,1,n)
    			{
    				if(u == v || have[v][s[u][v] - 'a'] == -1)	continue;
    				int z = have[v][s[u][v] - 'a'];
    				puts("YES");
    				vector<int> cire = {v,z,v,u},ciro = {u,v,z,v};
    				int p = 1;
    				if((m / 2) % 2 == 0)
    				{
    					printf("%d ",v);
    					forn(i,1,m)
    					{
    						printf("%d ",cire[p++]);
    						if(p == 4)	p = 0;
    					}
    				}
    				else
    				{
    					printf("%d ",u);
    					forn(i,1,m)
    					{
    						printf("%d ",ciro[p++]);
    						if(p == 4)	p = 0;
    					}
    				}
    				puts("");
    				ok = 1;
    				break;
    			}
    			if(ok)	break;
    		}
    		if(!ok)	puts("NO");
    	}
    	return 0;
    }
    

    E. Sorting Books

    题目大意:有(n)本书,每本书有自己的颜色.给定一个操作:把一本书移动到所有书的末尾.求最少操作多少次可以让所有相同颜色的书放在一起.

    思路

    这个题的idea是这题1367F2 flyingsort的弱化.这个转移还是比较独特的,没见过可能做不出来.

    可以发现这个题的要点是对每本书操作怎么做,也就对应决策这件事,可以想到用dp来做每一步的决策,实际移动肯定是不可能的,那么顺序的考虑dp会很复杂,因为往后的时候还需要考虑前面有哪些元素移动到了末尾,这件事情很麻烦,所以一个现在的方向是从后往前dp.

    问题就是dp看的是什么.如果去考虑每个元素要不要移动到末尾道理是讲不通的,因为你不知道这个颜色对应的整体的形式是长什么样的,那么另外一个关键点就是这个dp他想要求的东西必须要直接或者间接地看出来每个颜色的书具体在序列里面是不是合理的,他们分布是什么样的.顺着这个思路可以考虑:(f[i])表示把从(i)开始的后缀里面所有的颜色都合并到一起的代价,但是这个也做不了,因为无从得知这个后缀里面的颜色如果还存在相同颜色且不在这个后缀里面,后续拼接上的代价.但是不妨把这个状态表示反转一下:

    • 状态:(f[i])表示对于(i)开始的后缀,里面最多有多少个元素不需要移动
    • 入口:全(0)即可
    • 转移:在转移的时候,我们其实只需要考虑上一个状态(f[i+1])与之有什么不同:也就是加入了一个(a_i).那么假如(i)这个位置是(a_i)这个颜色第一次出现的位置,那么转移的重点只在于:保留(a_i)都不变化和不保留(a_i)的操作区别,分别对应(f[i] = cnt[a[i]] + f[R[a[i]] +1])以及(f[i] = f[i + 1]),其中这个(R[a[i]])表示(a[i])这个颜色最后的位置,(cnt[a[i]])表示的是在整个序列里面某个颜色出现的次数(后一个转移看形式其实也看得出来,他实际上对应的就是什么事都不干的转移方式,后续讨论的时候就不再提了).接下来考虑,那如果(a_i)又不是第一次出现的,他之前还有,怎么转移?是不是就跟上面第一个粗糙的想法里提到的转移方程也是一样不能处理后续的情况的呢?事实上不是,这个转移方程好就好在他可以接上之后的转移,因为他只关注序列里面有多少个,而不关注他的具体位置.这个情况的转移就是(f[i] = cur\_cnt[a[i]]),其中(cur\_cnt)表示的意思是当前这个后缀里面颜色的个数,而不是整个序列里面的个数.
    • 出口:(res = n - f[1])

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define forn(i,x,n) for(int i = x;i <= n;++i)	
    #define forr(i,x,n)	for(int i = n;i >= x;--i)
    
    const int N = 5e5+7;
    int f[N],cnt[N];
    int L[N],R[N],a[N];
    
    int main() 
    {
    	int n;scanf("%d",&n);
    	forn(i,1,n)	scanf("%d",&a[i]);
    	
    	forn(i,1,n)
    	{
    		if(!L[a[i]])	L[a[i]] = i;
    		R[a[i]] = i;
    	}
    	
    	forr(i,1,n)
    	{
    		++cnt[a[i]];
    		if(i == L[a[i]])	f[i] = max(f[i],cnt[a[i]] + f[R[a[i]] + 1]);
    		else 	f[i] = max(f[i],cnt[a[i]]);
    		
    		f[i] = max(f[i],f[i + 1]);
    	}
    	
    	printf("%d",n - f[1]);
    	return 0;
    }
    
  • 相关阅读:
    使用ngx_lua构建高并发应用(1)
    nginx+lua项目学习
    学习乱
    if---(switch-case)语句初步学习总结
    数据类型转换
    总结:C#变量,占位符等相关知识
    学习随笔
    开始我的.NET的学习旅程
    Python 网络爬虫 008 (编程) 通过ID索引号遍历目标网页里链接的所有网页
    Python 网络爬虫 007 (编程) 通过网站地图爬取目标站点的所有网页
  • 原文地址:https://www.cnblogs.com/HotPants/p/14381362.html
Copyright © 2011-2022 走看看