codeforces #531(div3)解题报告 Apare_xzc

#531 (div3)解题报告

531div3链接
CF group链接
完成时间:2019/2/27晚上+28号中午

这套题前5道都比较简单,都是看懂题以后就有思路的,D题情况稍微有点儿多,代码写的比较长,F题是个状态压缩dp,我暴力做的,昨天晚上妥妥地TLE了,主要想说一下F题

A.Integer Sequence Dividing
题意:
给一个正整数n,把1,2.3…n这n个数分到两个集合A,B中,sum(A)表示集合A中所有数之和,sum(B)同理,求sum(A)和sum(B)差的绝对值的在最小值
分析:
送分题,只需要求1+2+…n是奇数还是偶数即可,是偶数的话答案就是0,否则答案是1


B.Array K-Coloring
题意:
题意有点儿不是很好读懂。意思是给k(编号1到k)种颜色,n个物品,每个物品有一个数值。
现在要求给每个物品上色,要求数值相同的物品不能涂相同的颜色,且不能有某种颜色没有用到。
如果有满足题目要求的涂色方式,输出YES(当时wa的那一发就是因为没有输出YES),然后按顺序输出每个物品的颜色(任意一种方案即可)。
如果做不到,直接输出NO。
分析:
题目有两个限制条件,要保证所有颜色都要用到必须满足n>=k,要满足数值相同的物品不同色必须满足同一数值的物品数不大于k。
满足这两个条件,就有答案。开一个结构体数组,记录id,按数值排序,循环涂色,然后再按id排序还原,直接输出颜色。


我的AC代码

/*
2019-02-27 15:54:28	apare	B - Array K-Coloring	GNU C++17	Accepted	31 ms	1200 KB
*/
#include <bits/stdc++.h> 
#define LL long long 
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
#define Mst(a,b) memset(a,(b),sizeof(a))
using namespace std;
const int maxn = 1e5;
const int mod = 1e9+7;
struct Node{
	int id,x,color;
}node[maxn]; 
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	For(i,1,n)
		scanf("%d",&node[i].x),node[i].id = i;
	if(k>n)
	{
		cout<<"NO"<<endl;
		return 0;	
	}        
	sort(node+1,node+1+n,[](Node a,Node b){return a.x<b.x;});
	int cnt = 1;
	int tot = 1;
	int kk = 1;
	node[1].color = 1;
	node[n+1].x = 5002;
	node[n+1].id = 100000; 
	For(i,2,n+1)
	{
		if(++kk>k) kk = 1;
		node[i].color = kk;
		if(node[i].x!=node[i-1].x)
		{
			tot = max(tot,cnt),cnt = 1;
			if(tot>k) 
			{
				cout<<"NO
";return 0;
			}
		} 
		else ++cnt;
	}
	sort(node+1,node+1+n,[](Node a,Node b){return a.id<b.id;
	});
	printf("YES
%d",node[1].color);
	For(i,2,n)
		printf(" %d",node[i].color);
	printf("
");

	return 0;
}

C.Doors Breaking and Repairing
题意:
有n个门,每个门有一个初始的使用年限ai(1<=ai<=1e9),你和另一个人轮流操作。你先手,搞破坏,对某一扇门的损害为x,对方后手,修门,增加某扇门的使用年限y个单位的值。如果你的伤害>=门的年限,那么门的年限变为0,报废且无法修复。
现在有接近无穷多的回(10^100),你尽可能希望使更多门报废,对方尽可能使更少的门报废。两人都很“聪明”。问最后最多几扇门可以被你报废掉?
分析:
如果你的破坏值x比他的修复值y要大,那么架不住回合太多,门一定会都被你报废掉。
如果你的破坏值x<=y,那么你破坏那一扇门,他就跟着修复那一扇门,除非你能一步到位,一次把门年限降为0,否则门的年限只会越来越长。统计ai<=x的门的个数为b,那么你能报废掉一半,如果b是奇数,你能报废(b+1)/2,因为你是先手

我的AC代码

/*
2019-02-27 15:58:39	apare	C - Doors Breaking and Repairing	GNU C++17	Accepted	31 ms	400 KB
*/
#include <bits/stdc++.h> 
#define LL long long 
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
#define Mst(a,b) memset(a,(b),sizeof(a))
using namespace std;
const int maxn = 1e5+100;
const int mod = 1e9+7;
int n,x,y;
int a[maxn];
int main()
{
	scanf("%d%d%d",&n,&x,&y);
	For(i,0,n-1) scanf("%d",a+i);
	if(x>y)
	{
		cout<<n<<endl;
		return 0; 
	}
	int cnt = 0; 
    for(int i=0;i<n;i++)
	{
		if(a[i]<=x) cnt++;
	}   
	int ans = cnt/2+cnt%2;
	cout<<ans<<endl; 

	return 0;
}

D.Balanced Ternary String
题意:
给定一个长度为3*n(n<=1e5)的字符串,字符串只包含’0’, ‘1’, '2’三种字符。现在你要置换掉某些字符为{‘0’,‘1’,‘2’}中的某个字符,是得新的字符串中三种字符的个数相同。要求置换的次数最少。输出新的字符串。
如果有多个答案,输出 字典序最小的字符串
分析:
显然是贪心。
情况稍微有一点儿多。

  • 先把字符串遍历一遍,统计数三种字符分别出现的次数

  • 设最终三个字符出现的次数都要为x

  • 若三种字符出现的情况本来就一样,直接输出原串

  • 若三种字符有一种出现次数为x,那么肯定另外两种一个多一个少。多的要变成少的。若多的字符字典序较少的字符大,那么从后往前置换,反之从前往后置换。

  • 若有两个字符次数>x,一个<x(两多一少) 那么要把两个多的都变为少的。还是贪心,不想赘述了…见代码吧

  • 若有两个次数<x,两个>x,(两少一多)…见代码吧

我的AC代码

/*
2019-02-27 17:19:24	apare	D - Balanced Ternary String	GNU C++17	Accepted	46 ms	300 KB
*/
#include <bits/stdc++.h> 
#define LL long long 
#define For(i,s,b) for(int i=(s);i<=(b);++i)
#define Rep(i,s,b) for(int i=(s);i>=(b);--i)
#define Mst(s,b) memset(s,(b),sizeof(s))
using namespace std;
const int maxn = 3e5+50;
char s[maxn];
int cnt[3];
int r[3] = {0,1,2};
bool cmp(int a,int b)
{
	return cnt[a]<cnt[b];
}
int main()
{
	int n;
	scanf("%d%s",&n,s+1);
	int x = n/3;
	For(i,1,n)
	{
		++cnt[s[i]-'0'];
	} 
	if(cnt[0]==cnt[1]&&cnt[1]==cnt[2])
	{
		printf("%s
",s+1);
		return 0;
	}
	////////////////////////////////////////////////////////
	int dif = 0;
	if(cnt[0]==x)
	{
		if(cnt[1]>cnt[2]) //要把1变成2 
		{
			dif = cnt[1]-x;
			Rep(i,n,1)
			{
				if(s[i]=='1')  s[i] = '2',--dif;
				if(dif==0)
				{
					printf("%s
",s+1);
					return 0;
				}
			}	
		}
		else if(cnt[2]>cnt[1])
		{
			dif = cnt[2]-x;
			For(i,1,n)
			{
				if(s[i]=='2') s[i] = '1',--dif;
				if(dif==0)
				{
					printf("%s
",s+1);
					return 0;
				}
			}
		}
	} 
	if(cnt[1]==x)
	{
		if(cnt[0]>cnt[2])
		{
			dif = cnt[0]-x;
			Rep(i,n,1)
			{
				if(s[i]=='0') s[i] = '2',--dif;
				if(dif==0)
				{
					printf("%s
",s+1);
					return 0;
				}
			}	
		}
		else if(cnt[0]<cnt[2])
		{
			dif = cnt[2]-x;
			For(i,1,n)
			{
				if(s[i]=='2') s[i] = '0',--dif;
				if(dif==0)
				{
					printf("%s
",s+1);
					return 0;
				}
			}
		}
	}
	if(cnt[2]==x)
	{
		if(cnt[1]>cnt[0]) //1变0 
		{
			dif = cnt[1] - x;
			For(i,1,n)
			{
				if(s[i]=='1') s[i] = '0',--dif;
				if(dif==0)
				{
					printf("%s
",s+1);
					return 0;
				}
			}
		}
		else if(cnt[0]>cnt[1])//0变1 
		{
			dif = cnt[0]-x;
			Rep(i,n,1)
			{
				if(s[i]=='0') s[i] = '1',--dif;
				if(dif==0)
				{
					printf("%s
",s+1);
					return 0;
				}
			}	
		}
	}
	sort(r,r+3,cmp);
	int small = r[0];
	int hadreduce[3] = {0};
	///////////////////////////////////////////////////////////
	if(cnt[r[1]]>x&&cnt[r[2]]>x) //一小两大 
	{
		hadreduce[r[1]] = cnt[r[1]] - x;
		hadreduce[r[2]] = cnt[r[2]] - x;
		dif = x-cnt[small];
		if(small==0) //0最少 
		{
			For(i,1,n)
			{
				if(s[i]=='0') continue; 
				if(hadreduce[s[i]-'0']>0) --dif,hadreduce[s[i]-'0']--,s[i] = '0';
				if(dif==0)
				{
					printf("%s
",s+1);
					return 0;
				}
			}
		}
		else if(small==2) //2最少,要把1和0变2  
		{
			Rep(i,n,1)
			{
				if(s[i]=='2') continue;
				if(hadreduce[s[i]-'0']>0) --dif,hadreduce[s[i]-'0']--,s[i] = '2';
				if(dif==0)
				{
					printf("%s
",s+1);
					return 0;
				}
			}
		}
		else  //1最少,要把0和2变成1 
		{
			For(i,1,n)
			{
				if(hadreduce[2]==0) break;
				if(s[i]=='1') continue; 
				if(s[i]=='2'&&hadreduce[2]>0) s[i] = '1',--dif,hadreduce[2]--;
			} 
			Rep(i,n,1)
			{
				if(hadreduce[0]==0) break;
				if(s[i]=='1') continue; 
				if(s[i]=='0'&&hadreduce[0]>0) s[i] = '1',--dif,hadreduce[0]--;
			}
			printf("%s
",s+1);
				return 0;
		}
	}
	////////////////////////////////////////////////////////////////////////////////
	if(cnt[r[0]]<x&&cnt[r[1]]<x) //一大两小 
	{
		int waitadd[3]={0};
		int big = cnt[r[2]]-x;
		waitadd[r[1]] = x-cnt[r[1]];
		waitadd[r[0]] = x-cnt[r[0]]; 
		if(r[2]==0) //0变为1和2 
		{
			Rep(i,n,1)
			{
				if(s[i]!='0') continue;
				if(waitadd[2]>0) --waitadd[2],--big,s[i] = '2';
				else if(waitadd[1]>0) --waitadd[1],--big,s[i] = '1';
				else break;
				
			}
			printf("%s
",s+1);
			return 0;
		}
		else if(r[2]==2) //2变为0和1 
		{
			For(i,1,n)
			{
				if(s[i]!='2') continue;
				if(waitadd[0]>0) --waitadd[0],--big,s[i] = '0';
				else if(waitadd[1]>0) --waitadd[1],--big,s[i] = '1';	
				else break;
			} 
			printf("%s
",s+1);
			return 0;	
		}
		else if(r[2]==1) //1变为0和2 
		{
			For(i,1,n)
			{
				if(waitadd[0]==0) break;
				if(s[i]=='1') 
				{
					waitadd[0]--,big--,s[i] = '0';	
				}	
			} 
			Rep(i,n,1)
			{
				if(waitadd[2]==0) break;
				if(s[i]=='1') 
				{
					waitadd[2]--,big--,s[i] = '2';	
				}	
			}
			printf("%s
",s+1);
			return 0;	
		}
	} 
	return 0;
}

E.Monotonic Renumeration 题意:
给一组数a[n],n个(2<=n<=2e5),现在要求构造一个长度同样为n的数列b,要求:
1.若a中两个数相同,则b中相同位置的两个数也要相同。
2.b中第一个数必须为0,后面的数只能选择和前一个相同或者比前一个大1,求能构造出的数列的个数(对998244353取模)
分析:

  • b中的数只能和前一个一样或者比前一个大1,如果没有第一个条件的限制,那么从第2个数到第n个数每个数都有2中选择,答案就是2^(n-1)%mod
  • 第一个条件告诉我们,如果数列a中a[i]和a[j]相同,那么b[i]和b[j]也要相同。由于数列b是单调不减的,所以b[i],b[i+1]…b[j]这一段所有的元素都必须相同。
  • 所以,我们只需要找出所有的相同元素的位置区间的集合,求全集[1,n]和这个集合去差集,差集的大小就是我们可以自由取值的元素的个数。
  • 但是我做的时候想,如果暴力把所有相同的位置都找出来,肯定有极限数据可以卡掉,不TLE也会MLE。如2e5里面有x = 1e5个相同的数,岂不是要找(x)*(x-1)/2个区间?
  • 但事实上,如果数值相同的元素在数列的不同位置出现了多次,我们只需要找到某个元素在最前面出现的位置和在最后面出现的位置即可。
  • 用map记录置为x的元素的个数,首次出现的位置St和最后出现的位置Ed.把区间[St,Ed]push到vector中,最后排序求差集,快速幂即可

我的AC代码

/*
2019-02-27 17:42:52	apare	E - Monotonic Renumeration	GNU C++17	Accepted	234 ms	15600 KB
*/
#include <bits/stdc++.h> 
#define LL long long 
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
#define Mst(a,b) memset(a,(b),sizeof(a))
using namespace std;
const int maxn = 1e6+50;
const int mod = 998244353;
void print(pair<int,int> & x)
{
	cout<<"("<<x.first<<","<<x.second<<")
";
}
void fast_pow(LL a,LL b)
{
	LL ans = 1;
	while(b)
	{
		if(b&1) ans = ans*a%mod;
		a = a*a%mod;
		b>>=1;
	}
	cout<<ans<<endl;
}
int a[maxn]; 
map<int,int> mp;
map<int,int> st;
map<int,int> seen;
map<int,int> cnt;
vector<pair<int,int> >v;
int main()
{
	int n;
	scanf("%d",&n);
	For(i,1,n) 
	{
		scanf("%d",a+i);
		++cnt[a[i]];
	}
	For(i,1,n)
	{
		if(cnt[a[i]]==1) continue;
		if(!seen[a[i]]) st[a[i]] = i;
		if(++seen[a[i]]==cnt[a[i]]) v.push_back(make_pair(st[a[i]],i));
	}
	if(v.empty())
	{
		fast_pow(2ll,n-1);
		return 0;
	}
	sort(v.begin(),v.end());
	int cntt = v[0].first-1;
	int left = v[0].first;
	int right = v[0].second;
	for(auto &x:v)
	{
	//	print(x);
		if(x.first>right)
		{
			cntt += x.first-right;
			right = x.second;
			left = x.first;
		}
		else 
		{
			right = max(right,x.second);
		}
	}
	cntt += n-right;
	fast_pow(2ll,cntt);

	return 0;
}

F.Elongated Matrix
题意:
给一个n行m列的矩阵,你可以交换任意两行无限次。交换后对与每一列从上往下遍历,遍历到该列的最后一个元素后以下一列的第一行的元素重新开始,从上往下走。最后生成一个长度为m乘以n的数列,设x为该某个数列任意两个相邻元素之差的绝对值的最小值。求x的最大值。
分析:
先算一下所有的行的排列的数目,是16!
我先写了一发暴力dfs,果然TLE了… 我真的会算复杂度
这道题正确的做法是状压dp。把每一行看作是一个节点。就是要求一条含有n个节点的路径。
可以用一个整数记录一个人状态。16行对应16个节点。一个整数的二进制形式有几个1,说明已经选了几行。二进制的第k为代表第k行。
那么我们可以从1到(1<<n)进行递推。
令dp[i][j][k]表示状态为i,起点为j,终点为K的“距离”
如某个状态的二进制形式为010011,那么说明第1行,第2行和第5行已经被选择了。但是我们不知道顺序
所以我们从这3个1中选出所有可能的当前路径起点和终点(从1里面挑),然后由终点继续向没有走过的顶点(状态中的0的)延伸。
从小到大dp即可。

我的AC代码

//2019-02-28 14:35:51	apare	F - Elongated Matrix	GNU C++17	Accepted	358 ms	66500 KB
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Mst(a,b) memset(a,(b),sizeof(a))
using namespace std;
int dp[1<<16][16][16],a[16][10001],ed,m,n,ans;
int MinDisRow[16][16],MinDisHeadTail[16][16];
void init();void DP();void Solve();void reinit();
int main()
{
	std::ios::sync_with_stdio(false);
	while(cin>>n>>m)
	{
		For(i,0,n-1) For(j,0,m-1) cin>>a[i][j];
		if(n==1)
		{
			ans = 2e9;
			For(j,1,m-1) ans = min(ans,abs(a[0][j]-a[0][j-1]));
			cout<<ans<<endl;;continue;
		}
		init();DP();Solve();reinit();
	}
	return 0;
}
void init()
{
	ans = 0;
	ed = (1<<n)-1;
	For(i,0,n-1)
	{
		For(j,0,n-1)
		{
			if(i==j) continue;
			MinDisRow[i][j] = MinDisHeadTail[i][j] = 2e9;
			For(k,0,m-1) MinDisRow[i][j] = min(MinDisRow[i][j],abs(a[i][k]-a[j][k]));
			For(k,1,m-1) MinDisHeadTail[i][j] = min(MinDisHeadTail[i][j],abs(a[i][k]-a[j][k-1]));
		}
	}
}
void DP()
{
	For(i,1,ed-1)   
	{
		vector<int> v[2];
		int bit = 0;
		For(j,0,n-1)
		v[(i&(1<<j))>0].push_back(j);
		if((int)v[1].size()==1)  
		{
			bit = v[1][0];
			For(j,0,n-1)
			{
				if(j==bit) continue;
				dp[i|(1<<j)][bit][j] = max(dp[i|(1<<j)][bit][j],MinDisRow[bit][j]);
			}
		}
		else
		{
			int cnt0 = v[0].size()-1;
			int cnt1 = v[1].size()-1;
			int j,k,p;
			For(jj,0,cnt1)
			{
				j = v[1][jj];
				For(kk,0,cnt1)
				{
					k = v[1][kk];
					if(j==k) continue;
					For(pp,0,cnt0)   //j->...->K + p ==> j->...->K->p
					{
						p = v[0][pp];
						dp[i|(1<<p)][j][p] = max(dp[i|(1<<p)][j][p],min(dp[i][j][k],MinDisRow[k][p]));
					}
				}
			}
		}
	}
}
void Solve()
{
	For(j,0,n-1)
		For(k,0,n-1)   
		{
			if(j==k) continue;
			ans = max(ans,min(dp[ed][j][k],MinDisHeadTail[j][k]));
		}
	cout<<ans<<endl;
}
void reinit()
{
	Mst(dp,0);
	Mst(MinDisRow,0);
	Mst(MinDisHeadTail,0);
}

当时看了别人的题解,还写了个有vis数组的,但是写解题报告的时候觉得完全不用写vis数组,但是时间稍微满了一点点。358ms
中午交的用了vis数组的代码 (218ms)
还有暴力的代码 (TLE)


原文地址:https://www.cnblogs.com/Apare-xzc/p/12243659.html