第一篇博客:那些年磕过的题之Fence Painting


前言

之前从没有过要写博客的想法,觉得会写博客的都是大佬,自己的水平还是太菜。这个学期末和大佬们一起学习写大作业肝课设,他们也不止一次的建议我可以试着写博客。明天就是大年三十了,想着新的一年要有新气象,所以打算在今天写下第一篇博客,就像大佬说的,万事开头难,写了总比没写好。正好最近为了实现元旦那天立下的flag,打了CF刷了题,这道Fence Painting我前前后后也改了差不多两个晚上才AC,总共提交了差不多20次左右,答案错误,超时,超内存都出来了,所以记录一下我对这道题的解法,就当做笔记了。


一、题目

英文原题

C. Fence Painting
time limit per test:2 seconds
memory limit per test:256 megabytes
input:standard input
output:standard output
You finally woke up after this crazy dream and decided to walk around to clear your head. Outside you saw your house's fence — so plain and boring, that you'd like to repaint it.

You have a fence consisting of n planks, where the i-th plank has the color ai. You want to repaint the fence in such a way that the i-th plank has the color bi.

You’ve invited m painters for this purpose. The j-th painter will arrive at the moment j and will recolor exactly one plank to color cj. For each painter you can choose which plank to recolor, but you can’t turn them down, i. e. each painter has to color exactly one plank.

Can you get the coloring b you want? If it’s possible, print for each painter which plank he must paint.

Input
The first line contains one integer t (1≤t≤104) — the number of test cases. Then t test cases follow.

The first line of each test case contains two integers n and m (1≤n,m≤105) — the number of planks in the fence and the number of painters.

The second line of each test case contains n integers a1,a2,…,an (1≤ai≤n) — the initial colors of the fence.

The third line of each test case contains n integers b1,b2,…,bn (1≤bi≤n) — the desired colors of the fence.

The fourth line of each test case contains m integers c1,c2,…,cm (1≤cj≤n) — the colors painters have.

It’s guaranteed that the sum of n doesn’t exceed 105 and the sum of m doesn’t exceed 105 over all test cases.

Output
For each test case, output “NO” if it is impossible to achieve the coloring b.

Otherwise, print “YES” and m integers x1,x2,…,xm, where xj is the index of plank the j-th painter should paint.

You may print every letter in any case you want (so, for example, the strings “yEs”, “yes”, “Yes” and “YES” are all recognized as positive answer).

中文翻译

(百度直接翻译的有些地方的表述有点问题,所以稍微把翻译不对或者不通顺的地方自己修改了一下,但是因为我的六级到现在也还没过(捂脸),所以有不通顺的地方大家可以看看英文原文)

在做了这个疯狂的梦之后,你终于醒了过来,决定四处走走,清醒一下头脑。在外面你看到了你家的篱笆——如此朴素乏味,以至于你想重新粉刷一下。

你有一个由n块木板组成的围栏,其中第i块木板的颜色是ai。你想重新粉刷篱笆,使第i块木板的颜色为bi。

你为此邀请了m个画家。第j个油漆工将在第j时刻到达,并将给正好一块木板重新着色为cj。对于每个画家你可以选择哪个木板重新着色,但你不能拒绝他们,即每个画家只能给一块木板涂色。

你能得到你想要的颜色吗?如果可能的话,为每个画家打印他必须画的木板。

输入

第一行包含一个整数t(1≤t≤104)-测试用例数。接着是t测试用例。

每个测试用例的第一行包含两个整数n和m(1≤n,m≤105)-围栏中木板的数量和油漆工的数量。

每个测试用例的第二行包含n个整数a1,a2,…,an(1≤ai≤n)-围栏的初始颜色。

每个测试用例的第三行包含n个整数b1,b2,…,bn(1≤bi≤n)-栅栏的所需颜色。

每个测试用例的第四行包含m个整数c1,c2,…,cm(1≤cj≤n)-画家拥有的颜色。

在所有测试用例中,保证n的和不超过105,m的和不超过105。

输出

对于每个测试用例,如果无法实现着色b,则输出“NO”。

否则,打印“是”和m整数x1,x2,…,xm,其中xj是第j位画家应该绘制的木板的索引。

您可以在任何情况下打印每个字母(例如,字符串“yEs”、“yEs”、“yEs”和“yEs”都被认为是肯定答案)。

输入样例

6
1 1
1
1
1
5 2
1 2 2 1 1
1 2 2 1 1
1 2
3 3
2 2 2
2 2 2
2 3 2
10 5
7 3 2 1 7 9 4 2 7 9
9 9 2 1 4 9 4 2 3 9
9 9 7 4 3
5 2
1 2 2 1 1
1 2 2 1 1
3 3
6 4
3 4 2 4 1 2
2 3 1 3 1 1
2 2 3 4

输出样例

YES
1
YES
2 2
YES
1 1 1
YES
2 1 9 5 9
NO
NO

二、解题思路

打比赛的时候思路比较乱,想到一点补一点,所以后来补题的时候重新理了一下思路。

首先先判断是否需要改变颜色,即木板原来的颜色与最终的颜色是否有不一样的地方,若有,flag1=1,并判断油漆工是否有需要的颜色,若有,则flag2=1。例如:木板原来的颜色a为1,1,2,3,4;需要的颜色b为1,1,2,3,5;油漆工所持颜色c为1,2,4;则没有所需要的颜色5,故不可能有解决方案,则flag2=0。注意这里不仅要判断是否有需要的颜色,还需要判断油漆工所持颜色的数量,因为每个油漆工只能给一块木板着色,所以若有多块需要修改为某种颜色的木板,则油漆工所持该颜色需达到指定数量才可以。例如:木板原来的颜色a为1,1,2,3,4;需要的颜色b为1,1,5,3,5;油漆工所持颜色c为1,2,5;因为只有一个油漆工有颜色5,但有两块木板需涂色为颜色5,所以该情况下没有解决方案。

若所有需要修改颜色的木板都有油漆工持有对应颜色,则接着判断每个油漆工所持颜色的用途。用途有两种情况:一是用来改色,二是可有可无,即最好没有。若是第一种用来改色的情况,则直接涂在需要修改颜色的木板上就行了。若是第二种情况,首先判断是否有与油漆工所持颜色相同颜色的木板,若有,则直接涂在对应颜色的木板上,flag4=1,涂过之后木板的颜色不变;若没有,则flag4=0,需要再次判断涂上错误的颜色之后有没有正确的颜色可覆盖掉错误的颜色,例如:某块木板x原来的颜色为1,需修改为2,某油漆工有颜色3,若该油漆工到达的顺序在持有颜色2的油漆工之前,则可先在x木板上涂上颜色3,等持有颜色2的油漆工到达可用颜色2覆盖颜色3,这样对最终的结果就没有影响了。若有可覆盖的颜色,则flag5=1;若没有正确的颜色可覆盖,则flag5=0,没有解决方案。

以上就是需要修改颜色的情况,若不需要修改颜色,则flag1=0。不需要修改颜色的情况比较简单,我们只需要判断油漆工涂色后是否可以维持原样,具体做法与需要修改颜色的情况中对于油漆工的颜色用途为可有可无的情况做法一致,因为对于不需要修改颜色的情况来说,所有颜色都是可有可无的。

以下是我改掉WA后的代码,答案是对了,但是在第四个测试点超时了

#include <stdio.h>
#include <bits/stdc++.h>

using namespace std;

int a[100005]={0};
int b[100005]={0};
int c[100005]={0};
int d[100005]={0};

int main()
{
	int t,n,m;
	int k=0;
	int i,j,p;
	int x,y;
	int flag=1;
	int flag1=0;
	int flag2=0;
	int flag3=0;
	int flag4=0;
	int flag5=0;
	scanf("%d",&t);
	for(i=0;i<t;i++){
		scanf("%d %d",&n,&m);
		flag=1;
		flag1=0;
		flag2=0;
		flag3=0;
		flag4=0;
		flag5=0;
		for(j=0;j<n;j++){
			scanf("%d",&a[j]);
		}
		for(j=0;j<n;j++){
			scanf("%d",&b[j]);
		}
		for(j=0;j<m;j++){
			scanf("%d",&c[j]);
			d[j]=0;
		}

		for(j=0;j<n;j++){
			if(a[j]!=b[j]){
				flag1=1;
				flag2=0;
				for(k=0;k<m;k++){
					if(c[k]==b[j]&&d[k]==0){
						flag2=1;
						d[k]=j+1;
						break;
					}
				}
				if(flag2==0){
					flag=0;
					break;
				} 
				
			}
		}
		for(k=0;k<m;k++){
			if(d[k]==0){
				for(j=0;j<n;j++){
					if(b[j]==c[k]){
						d[k]=j+1;
						break;
					}
				}
			}
		}
		if(flag1==0){
			for(k=0;k<m;k++){	
				for(j=0;j<n;j++){
					if(c[k]==b[j]){	
						d[k]=j+1;
						break;
					}
				}
			}
			for(k=0;k<m;k++){
				if(d[k]==0){
					flag5=0;
					for(j=k+1;j<m;j++){
						if(d[j]!=0){
							flag5=1;
							d[k]=d[j];
							break;
						}
					}
					if(flag5==0){
						flag=0;
						break;
					}
				}
			}
			
			
		}else{
			for(k=0;k<m;k++){
				if(d[k]==0){
					flag4=0;
					for(j=0;j<n;j++){
						if(c[k]==b[j]){
							flag4=1;
							d[k]=j+1;
							break;
						}
					}
					if(flag4==0){
						flag5=0;
						for(j=k+1;j<m;j++){
							if(d[j]!=0){
								flag5=1;
								d[k]=d[j];
								break;
							}
						}
						if(flag5==0){
							flag=0;
							break;
						}
					}
				}
			}
			
		}
		if(flag==0){
			printf("NO");
		}else{
			printf("YES\n");
			printf("%d",d[0]);
			for(k=1;k<m;k++){
				printf(" %d",d[k]);
			}
		}
		printf("\n");
		
	}
	
	return 0;
 } 

里面有很多地方用到了双重循环,时间复杂度大概是O(nm),所以出现超时也在意料之中。

但是我太菜了(捂脸),只会用空间换时间,也正是因为用空间换时间所以后来试出了超出内存的答案,但总归最后终于AC了,真的感动。

优化的方法添加了三个数组e,f,g(当时懒得想命名了所以先用efg了)。e是一个二维数组,e[i][j]代表油漆工拥有颜色的所在位置,i代表颜色,j代表是第几个该颜色。例如:油漆工所持颜色为1,1,2,3,4,5,则e[1][0]=0,e[1][1]=1,e[2][0]=2,e[3][0]=3,e[4][0]=4,e[5][0]=5。f[i]代表油漆工所持颜色i的数量,以上例子中,f[1]=2,f[2]=1,f[3]=1,f[4]=1,f[5]=1。g[i]代表木板所需颜色即数组b中每种颜色出现的最后一个位置,例如:数组b为1,1,2,3,4,2,则g[1]=1,g[2]=5,g[3]=3,g[4]=4。

有了这三个数组后,有些原本需要双重循环判断的地方现在只需要一重循环就可以得出结果,这样就大大节省了时间。

在优化了一半的时候先提交了一次,没想到居然过了,后来把需要优化的地方都改了之后差不多快了10倍,但是不得不说内存消耗真的太大了(捂脸)


三、代码

#include <stdio.h>
#include <bits/stdc++.h>

using namespace std;

int a[100001]={0};//木板原来的颜色
int b[100001]={0};//木板最终的颜色
int c[100001]={0};//油漆工所持颜色
int d[100001]={0};//油漆工涂色位置
int e[100001][501];//油漆工有的颜色所在位置 
int f[100001]={0};//油漆工拥有的每种颜色数量 
int g[100001]={0};//最终每种颜色出现的最后一个位置 

int main()
{
	int t,n,m;
	int i,j,k;
	int flag=1;//是否有解决方案(默认有)
	int flag1=0;//判断是否需要涂色
	int flag2=0;//若需要改色,判断油漆工是否有需要的颜色
	//int flag3=0;//不需要了,忽略就好
	int flag4=0;//判断木板最终的颜色中是否有木板颜色与油漆工所持颜色相同
	int flag5=0;//判断木板涂上颜色后是否有正确颜色可覆盖
	scanf("%d",&t);
	for(i=0;i<t;i++){
		scanf("%d %d",&n,&m);
		flag=1;
		flag1=0;
		flag2=0;
		//flag3=0;
		flag4=0;
		flag5=0;
		for(j=0;j<n;j++){
			scanf("%d",&a[j]);
			g[j]=-1;
		}
		g[n]=-1;
		for(j=0;j<n;j++){
			scanf("%d",&b[j]);
			g[b[j]]=j;
		}
		for(j=0;j<m;j++){
			scanf("%d",&c[j]);
			d[j]=0;
			e[c[j]][f[c[j]]]=j;
			f[c[j]]++;
		}
		
		for(j=0;j<n;j++){
			if(a[j]!=b[j]){//现有与预期不一样 
				flag1=1;//需要改色 
				flag2=0;//默认油漆工没有需要颜色 
				if(f[b[j]]>0){//油漆工有颜色 
					for(k=0;k<f[b[j]];k++){
						if(d[e[b[j]][k]]==0){//该颜色还未使用 
							flag2=1;
							d[e[b[j]][k]]=j+1;
							break;
						}
					}
				}
				if(flag2==0){
					flag=0;
					break;
				} 
				
			}
		}
		if(flag!=0){
			for(k=0;k<m;k++){//保证每个木板有的颜色都被用到 
				if(d[k]==0){//如果有木板需要的颜色但还未用到 
					if(g[c[k]]!=-1){
						d[k]=g[c[k]]+1;
					}
				}
			}
			if(flag1==0){//不需要改色 
				for(k=0;k<m;k++){
					if(g[c[k]]!=-1){
						d[k]=g[c[k]]+1;
					}
				}
				for(k=0;k<m;k++){
					if(d[k]==0){//若有油漆工还未涂色(有不是木板所需颜色) 
						flag5=0;
						for(j=k+1;j<m;j++){
							if(d[j]!=0){//若在该油漆工之后有可覆盖的颜色 
								flag5=1;
								d[k]=d[j];
								break;
							}
						}
						if(flag5==0){
							flag=0;
							break;
						}
					}
				}
				
				
			}else{//需要改色 
				for(k=0;k<m;k++){
					if(d[k]==0){//可有可无的颜色 
						flag4=0;
						if(g[c[k]]!=-1){
							flag4=1;
							d[k]=g[c[k]]+1;
						}
						if(flag4==0){//没有木板颜色与之相同 
							flag5=0;
							for(j=k+1;j<m;j++){
								if(d[j]!=0){//在其后有颜色可覆盖 
									flag5=1;
									d[k]=d[j];
									break;
								}
							}
							if(flag5==0){
								flag=0;
								break;
							}
						}
					}
				}
				
			}
		}
		if(flag==0){
			printf("NO");
		}else{
			printf("YES\n");
			printf("%d",d[0]);
			for(k=1;k<m;k++){
				printf(" %d",d[k]);
			}
		}
		printf("\n");
		for(j=0;j<m;j++){
			f[c[j]]=0;
		}
	}
	
	return 0;
 } 



总结

写到这里已经过了0点了,总的来说这个方法感觉没有很好,但至少能过当时真的就很开心,特别是在看到一个又一个Wrong answer,Time limit exceeded,Memory limit exceeded甚至还有不知道为啥的Compilation error之后,绿色的AC真的可以算得上是惊喜了。其实这个方法还是有bug,比如说当每个油漆工所持单个颜色的个数超过500就会出问题,因为e数组只定义到e[100001][501],再大的话就会超内存了。然后编码习惯方面也有很大改进的空间,有些细节说不定可以再简化改进一下,还没有去看CF上其他大佬们的代码,等有时间去看看有没有其他更好的解题方法,这篇博客就当是磕了两个晚上的一个记录吧,最后给大家拜个早年,新年快乐!!新的一年冲冲冲!!

最后的最后,附上我多达21次的提交记录(捂脸),其中有几次提交是为了看样例的输入数据,改bug真的心态要崩了

在这里插入图片描述

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值