Educational Codeforces Round 114 (Rated for Div. 2)题解

还是常规的过了A,B,C还是在D上卡了...

D. The Strongest Build

简化题意:给定你n组东西,每组东西都有(c_i)个装备,每个装备有一个武力值(a_{i,j}),要求你从每一组中选出一个装备,使得总的装备的武力值最大。但有一些给定的方案是不能选的。
首先看到这个题的第一印象是只有(m)中方案是不能选的。那我们直接从最大的方案开始,一点点的调整为次大的,第三大的,第四大的,....,最多也就调整m次。但发现这个调整真的太难了...实现不了。其实想一下,这个题总的方案数也就(prod c_i)这么多。我们可以直接对其进行搜索。但直接搜索全部的方案显然不太现实,我们依据一下原则:
1:初始的策略就是先选每一组中的最大值。
2:若当前这个策略不合格,则将当前策略中的某一组,将它的装备降为下一级,形成一个新的策略。
3:对所有当前的策略,优先取出和最大的策略进行检查。若不合法,则按照2生成其他的策略,否则,直接退出当前值为最大值。
总的贪心的思路和dijkstra算法差不多,不过算法形式和应用范围相差却深远。
学习某个算法的形式真的解决不了太多问题,掌握了其真正的思路与核心才掌握了它的一切!

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,MN=15;
int n,m,c[MN],a[MN][N];
map<vector<int>,bool>mp;
map<vector<int>,bool>vis;
priority_queue<pair<int,vector<int> > >q;
int main()
{
//	freopen("1.in","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&c[i]);
		for(int j=1;j<=c[i];++j) scanf("%d",&a[i][j]);
	}
	vector<int>s;
	scanf("%d",&m);
	for(int i=1;i<=m;++i)
	{
		for(int j=1;j<=n;++j) 
		{
			int x;scanf("%d",&x);
			s.push_back(x);	
		}
		mp[s]=1;s.clear();
	}
	int s1=0;
	for(int i=1;i<=n;++i) s.push_back(c[i]),s1+=a[i][c[i]];
	vector<int>ans;q.push({s1,s});vis[s]=1;
	while(!q.empty())
	{
		pair<int,vector<int> >x=q.top();q.pop();
		if(mp.find(x.second)==mp.end()) {ans=x.second;break;}
		for(int i=1;i<=n;++i)
		{
			if(x.second[i-1]>=2)
			{
				x.second[i-1]--;
				if(vis.find(x.second)==vis.end())
				{
					q.push({x.first-a[i][x.second[i-1]+1]+a[i][x.second[i-1]],x.second});
					vis[x.second]=1;
				}
				x.second[i-1]++;
			}
		}
	}
	for(auto x:ans) printf("%d ",x);
	return 0;
} 

E. Coloring

这个E题真的好难搞啊!!!
要被虐哭了都,题解翻译过来根本看不懂,只能一边看官方题解代码,一边试图理解题解里的话。
只能慢慢的整理思路....
首先遇到这种题不要慌!!!先尝试着随便写写,列举下,找找规律,这种题肯定是找到某种性质,然后根据性质做题。
这个题就直接说了,首先根据随便写写,随便推推可以发现一个重要的性质(这只能靠内力了):
如果有两个同一行格子相邻且数字相同的话,那么他们这两个格子所在的两列就唯一确定了。
例如如果(3,3)和(3,4)都为1,则(2,3)和(2,4)只能为0,(4,3)和(4,4)只能为0,(1,3)和(1,4)只能为1,(5,3)和(5,4)只能为0,....可以一直推下去,这两列就唯一确定了(我们成为列条带)。如果是同一列中两个相邻数字相同,则这两行也唯一确定(我们成为横条带),和上述类似。
那么我们知道了两个相邻格子的会使两列唯一确定。接下来考虑什么情况下会出现两个相邻格子数字相同的情况,稍微推一下就能得知:1.当同一行有两个格子数字相同且中间的格子个数为偶数,那么这之间一定会有两个相邻的格子数字相同。比如(3,3)和(3,6)两个格子都是1,不管(3,4)和(3,5)怎么填,都会有两个相邻的格子数字相同的情况出现。2.当同一行有两个格子数字不同且中间的格子数为奇数的话,那么这之间一定会有两个相邻的格子数字相同。刺入(3,3)为1,(3,5)为0,不管你咋填,都一定会有两个相邻的格子数字相同。
同时我们会发现若同时有横条带和列条带的话,那么这两种条带一定会冲突,因为你会发现列条带相邻行是不同的,而横条带却要求相邻行要相同。最简单的例子就是一个(2 imes2)的格子,(1,1)和(1,2)都为1,那么根据列条带的话,则(2,1)和(2,2)也应该为0,但如果使(2,1)也为1则出现横条带两者冲突。
所以合法的局面是只能出现横条带或列条带,或不出现条带。
分开考虑:若不出现条带,即不能出现格子相邻且数字相同,发现这个一共就两种。0101010...和1010110全部错开就行。
列条带:我们考虑若有列条带的话,(即如果同一行有两个数字相同的格子他们中间的格子数为偶数((0)也算偶数)),这两个列条带就不用考虑了,其他的列考虑相邻的行都不能相同(否则就会出现横条带),发现一列就只有两种情况,(010101010....)(1010101010...)但如果这一列中有数字的话,那么这一列就从这两种情况中唯一确定了。设(nx)为没有数字的列,那么列的情况数就是(2^{nx}),横条带和列类似。同时我们发现我们如果有某一行出现产生列条带的条件,这两列也被唯一确定了,同时他们也因为相对应的列出现了数字而被唯一确定,他们对答案并不影响。换句话说我们根本没有必要去统计到底是哪些列出现了产生列条带的条件,我们只关注到底有没有哪一行出现产生列条带的条件,从而使得整个局面处于列条带的情况下。
那么我们怎么快速知道一行是否有相邻且数字相同的格子出现,我们发现这和格子的奇偶有关,我们只需要记录每行,每列格子中数字0/1处在奇数,偶数的位置的个数,结合这个和上面的两条判断条带出现的条件,当一个新的数来时,我们可以快速判断这一行这一列是否产生条带。
具体我们需要维护一下信息:
1.每个格子的颜色。(用map<pair<int,int>,int>解决。)
2.哪些行产生条带,哪些列产生条带了。支持动态删除和加入,统计数量。(用set解决。)
3.哪些行,列出现了数字。支持动态删除,加入,统计数量。(用set结局。)
这里的小细节就是有时候统计答案,可能会重复计算没有条带的情况。这里特判下即可。
最后代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10,P=998244353;
int n,m,k,cnth[N][2][2],cntl[N][2][2],cntx[2][2];
//cnth,cntl分别记录第i行/列数字0/1在奇数/偶数的位置上有多少个
//cntx这里是为了特判当横条带,列条带都没出现时,当前的数字能否确定出现没条带的情况。 
ll p[N];//p预处理2的次幂 
map<pair<int,int>,int>mp;//记录格子上的数字。 
set<int>sh,sl;//记录哪些行/列能因此形成条带 
set<int>uh,ul;//记录哪些行/列有数字 
inline bool check(int cnt[2][2])//判断这个行/列是否能形成条带。 
{
	if(cnt[1][0]>0&&cnt[1][1]>0||cnt[0][0]>0&&cnt[0][1]>0) return true;
	for(int i=0;i<=1;++i)
	{
		if(cnt[1][i]>0&&cnt[0][i]>0) return true;
	}
	return false;
}
inline void add(int x,int y,int t)//像某个格子添加数字 
{
	cnth[x][t][y&1]++;
	cntl[y][t][x&1]++;
	if(uh.find(x)==uh.end()) uh.insert(x);
	if(ul.find(y)==ul.end()) ul.insert(y);
	if(sh.find(x)==sh.end()&&check(cnth[x]))
		sh.insert(x);
	if(sl.find(y)==sl.end()&&check(cntl[y]))
		sl.insert(y);
	mp[{x,y}]=t;
	cntx[t][(x&1)^(y&1)]++;
}
inline void upd(int x,int y,int t)
{
	if(mp.find({x,y})!=mp.end())//先把这个格子的数删掉。 
	{
		cntx[mp[{x,y}]][(x&1)^(y&1)]--;//先删除这个格子的影响,以及这个行,列所在的信息。 
		cnth[x][mp[{x,y}]][y&1]--;
		cntl[y][mp[{x,y}]][x&1]--;
		uh.erase(x);ul.erase(y);
		if(sh.find(x)!=sh.end()) sh.erase(x);
		if(sl.find(y)!=sl.end()) sl.erase(y); 
		mp.erase({x,y}); 
		//以下重新统计该行该列的信息。
		if(cnth[x][0][0]||cnth[x][0][1]||cnth[x][1][0]||cnth[x][1][1]) uh.insert(x);
		if(cntl[y][0][0]||cntl[y][0][1]||cntl[y][1][0]||cntl[y][1][1]) ul.insert(y);
		if(check(cnth[x])) sh.insert(x);
		if(check(cntl[y])) sl.insert(y);
	} 
	if(t==-1) return;
	add(x,y,t);
	return;
} 
int main()
{
	//freopen("1.in","r",stdin);
	p[0]=1;
	for(int i=1;i<=1e6;++i) p[i]=p[i-1]*2%P;
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=k;++i)
	{
		int x,y,t;
		scanf("%d%d%d",&x,&y,&t);
		upd(x,y,t);
		ll ans=0;
		if(sh.size()>0&&sl.size()>0) ans=0;//即出现横条带也出现列条带,答案为0. 
		else if(sh.size()>0) ans=p[m-ul.size()];//只出现列条带,按照列条带的方式计算。 
		else if(sl.size()>0) ans=p[n-uh.size()];//只出现横条带。 
		else
		{
			if(ul.size()==0&&uh.size()==0) ans=(p[n]+p[m]-2+P)%P;//两个都没出现。 
			else
			{
				ans=(p[m-ul.size()]+p[n-uh.size()]+P)%P;
				if(cntx[1][0]+cntx[0][1]==0||cntx[0][0]+cntx[1][1]==0) ans=(ans-1+P)%P;
			}
		}
		printf("%lld
",ans);
	}
	return 0;
}

不用尽全力,你永远不会知道自己的潜力有多大!

原文地址:https://www.cnblogs.com/gcfer/p/15317980.html