题解 CF1369 D,E,F Codeforces Round #652 (Div. 2)

比赛链接

CF1369D TediousLee

题目链接

考虑每一轮新增的节点。在第\(i\)轮(\(i\geq 3\)):

  • 对于每个第\(i-1\)轮长出的节点,它现在没有儿子,所以它下面会新增\(1\)个第\(i\)轮的节点;
  • 对于每个第\(i-2\)轮长出的节点,它现在恰好有一个儿子(也就是第\(i-1\)轮的节点),所以它下面会新增\(2\)个第\(i\)轮的节点。

所以,如果记\(f[i]\)表示第\(i\)轮新长出的节点数量,则:\(f[i]=f[i-1]+2\cdot f[i-2]\) (\(i\geq 3\))。边界是\(f[1]=f[2]=1\)

现在我们已经能用\(f\)数组刻画出这棵树的样子了。考虑怎么求出最多覆盖的claw数量。

因为是在取模意义下运算,所以不能对数值比较大小,那就不太好DP。考虑贪心。

定义“一层”就是指同一轮加入的节点。以一个claw的根节点所在的层来代表它。我们先把最下面一层全取掉(也就是根节点在第\(n-2\)层的claw)。然后因为claw之间不能覆盖,所以下一个能取的是第\(n-5\)层。以此类推,后面能取的是:\(n-8\), \(n-11\) ... 层。所以每隔三层就能取一次,我们对\(f\)数组做一个隔\(3\)个的“前缀和”即可。

这个\(f\)数组和前缀和都能预处理出来。所以总时间复杂度\(O(n+t)\)。如果\(n\)特别大的话似乎也可以矩阵快速幂,复杂度就是\(O(t\log n)\)

参考代码:

//problem:CF1369D TediousLee
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

const int MAXN=2e6;
int f[MAXN+5],s[MAXN+5];

int main() {
	s[1]=f[1]=1;
	s[2]=f[2]=1;
	for(int i=3;i<=MAXN;++i){
		f[i]=mod1(f[i-1]+mod1(f[i-2]+f[i-2]));
		s[i]=mod1(f[i]+s[i-3]);
	}
	int T;cin>>T;while(T--){
		int n;cin>>n;
		if(n<=2)cout<<0<<endl;
		else cout<<(ll)mod1(f[n-2]+(n<5?0:s[n-5]))*4%MOD<<endl;
	}
	return 0;
}

CF1369E DeadLee

题目链接

对于一种食物\(i\),设有\(s_i\)个人喜欢它。把食物分为\(s_i\leq w_i\)(充足的)和\(s_i>w_i\)(不足的)两类。

根据每个人喜欢的两种食物,也可以将人分为:(1) 喜欢的两种食物都充足;(2) 一种充足,一种不足;(3) 喜欢的两种食物都不足,这三类。

考虑一个人如果是第(1)或第(2)类,那他随便往后排都可以,因为他总能吃到一个充足的食物。

我们先把这些人丢到队伍最后去,然后“删掉”。考虑删掉这些人后,每种食物的“充足/不足”情况可能会变化,也就是有一些原本“不足”的食物,会因为这些人的离开而变得“充足”。于是我们又可以删掉一批满足条件的人。以此类推,直到所有人都被删掉,或者无法再删掉任何一个人(无解)。

朴素实现这个过程是\(O(m^2)\)的,因为每轮(最坏情况下)只有一个人被删掉。

考虑优化这个过程。发现一个人被删掉后,只有他喜欢的两个食物会发生变化。所以只需要更新这两个食物即可。具体实现时,把喜欢每种食物的人装在一个\(\texttt{set}\)里,便于删除。再开一个队列,里面装“已经变得充足的食物”,每次弹出队首,更新喜欢这种食物的人。

时间复杂度\(O((n+m)\log n)\)

参考代码:

//problem:CF1369E DeadLee
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=1e5,MAXM=2e5;
int n,m,w[MAXN+5];
bool vis[MAXN+5];
struct Friend{int x,y;}a[MAXM+5];
set<int>s[MAXN+5];
int main() {
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		cin>>w[i];
	}
	for(int i=1;i<=m;++i){
		cin>>a[i].x>>a[i].y;
		s[a[i].x].insert(i);
		s[a[i].y].insert(i);
	}
	queue<int>q;
	for(int i=1;i<=n;++i){
		if(SZ(s[i])<=w[i])vis[i]=1,q.push(i);
	}
	vector<int>ans;
	while(!q.empty()){
		int x=q.front();q.pop();
		//cout<<x<<endl;
		while(!s[x].empty()){
			int i=*s[x].begin();
			//cout<<"* "<<i<<endl;
			assert(a[i].x==x||a[i].y==x);
			int y=(a[i].x==x?a[i].y:a[i].x);
			s[a[i].x].erase(i);
			s[a[i].y].erase(i);
			if(!vis[y] && SZ(s[y])<=w[y]){
				vis[y]=1;q.push(y);
			}
			ans.pb(i);
		}
	}
	if(SZ(ans)<m){
		cout<<"DEAD"<<endl;return 0;
	}
	cout<<"ALIVE"<<endl;
	for(int i=SZ(ans)-1;i>=0;--i)cout<<ans[i]<<" ";
	cout<<endl;
	return 0;
}

CF1369F BareLee

题目链接

先只考虑一轮游戏。求出先手能否成为赢家,能否成为输家(不论对手怎么操作):分别记为:\(\text{win}(s,e),\text{lose}(s,e)\)

\(\text{win}(s,e)\)

  • \(e\)是奇数时:如果\(s\)是奇数,则先手不能成为赢家,否则可以。

    证明可以归纳。当\(s=e\)时显然正确(不可能赢)。当\(s<e\)时,如果对\(s'>s\)都成立:若\(s\)是奇数,则\(2s\)\(s+1\)都是偶数(可以赢),所以\(s\)不能赢。若\(s\)是偶数,则\(s+1\)是奇数(不能赢)所以\(s\)可以赢。所以归纳假设正确。

  • \(e\)是偶数时:

    • 考虑如果\(2s>{e}\)。则没有\(\times2\)操作了,所以能赢当且仅当\(s\)是奇数。
    • 否则如果\(4s>e\)(也就是\(\frac{e}{4}<s\leq\frac{e}{2}\)),则先手可以做一次\(\times2\)操作,此时\(s\)变成偶数,后手必败。所以这种情况下先手必胜。
    • 否则,\(\text{win}(s,e)=\text{win}(s,\lfloor\frac{e}{4}\rfloor)\)。因为一旦\(s>\frac{e}{4}\),就是上一种情况,先手必胜,所以双方都不希望\(s>\frac{e}{4}\)。于是就等价于\(\text{win}(s,\lfloor\frac{e}{4}\rfloor)\)了。

\(\text{lose}(s,e)\)

  • 如果\(2s>e\),显然一次就可以直接输掉。
  • 否则,这个结果等价于\(\text{win}(s,\lfloor\frac{e}{2}\rfloor)\),因为只要一旦\(s>\frac{e}{2}\),对手直接输掉。所以双方都不希望\(s>\frac{e}{2}\)。于是就等价于\(\text{win}(s,\lfloor\frac{e}{2}\rfloor)\)了。

现在我们已经能够在\(O(\log e)\)的时间里求出单轮先手能不能赢/输。

现在要回答这个多轮的问题。依次考虑每一轮。维护出“当前轮结束之后,Lee能否成为赢家,能否成为输家(不论对手怎么操作)”。

如果上一轮结束后,Lee既可以成为赢家,又可以成为输家,则答案就是\(1\ 1\)。因为他可以自己选择做先手或后手,所以无论后面的游戏是什么,都能达到想要的结果。

如果上一轮结束后,两者都不可以,则答案就是\(0\ 0\)。因为无论他想要的结果是什么,对手都有可能阻止他。

排除这两种情况后,如果上一轮可以输,说明这一轮只能做先手,我们按Lee为先手的情况,调用\(\text{win}\)/\(\text{lose}\)函数,求一下即可。否则,这一轮只能做后手,我们调用两个函数后把结果取反即可。

时间复杂度\(O(t\log e)\)

参考代码:

//problem:CF1369F BareLee
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=1e5;
int n,can_win[MAXN+5],can_lose[MAXN+5];
struct game{ll s,e;}a[MAXN+5];

bool get_win(ll s,ll e){
	assert(s<=e);
	if(e%2==1){
		if(s%2==1)return 0;
		else return 1;
	}
	else{
		if(s*2>e){
			if(s%2==1)return 1;
			else return 0;
		}
		else if(s*4>e){
			return 1;
		}
		else{
			return get_win(s,e/4);
		}
	}
}
bool get_lose(ll s,ll e){
	if(s*2>e){
		return 1;
	}
	else{
		return get_win(s,e/2);
	}
}

int main() {
	cin>>n;
	for(int i=1;i<=n;++i)cin>>a[i].s>>a[i].e;
	can_lose[0]=1;
	for(int i=1;i<=n;++i){
		if(can_lose[i-1] && can_win[i-1]){
			cout<<1<<" "<<1<<endl;
			return 0;
		}
		if(!can_lose[i-1] && !can_win[i-1]){
			cout<<0<<" "<<0<<endl;
			return 0;
		}
		if(can_lose[i-1]){
			//这一局是先手
			can_win[i]=get_win(a[i].s,a[i].e);
			can_lose[i]=get_lose(a[i].s,a[i].e);
		}
		else{
			can_win[i]=(!get_win(a[i].s,a[i].e));
			can_lose[i]=(!get_lose(a[i].s,a[i].e));
		}
	}
	cout<<can_win[n]<<" "<<can_lose[n]<<endl;
	return 0;
}
posted @ 2020-06-24 14:41  duyiblue  阅读(501)  评论(0编辑  收藏  举报