zoukankan      html  css  js  c++  java
  • 题解 CF1369 D,E,F Codeforces Round #652 (Div. 2)

    比赛链接

    CF1369D TediousLee

    题目链接

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

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

    所以,如果记(f[i])表示第(i)轮新长出的节点数量,则:(f[i]=f[i-1]+2cdot f[i-2]) ((igeq 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(tlog 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_ileq w_i)(充足的)和(s_i>w_i)(不足的)两类。

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

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

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

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

    考虑优化这个过程。发现一个人被删掉后,只有他喜欢的两个食物会发生变化。所以只需要更新这两个食物即可。具体实现时,把喜欢每种食物的人装在一个( exttt{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

    题目链接

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

    ( ext{win}(s,e))

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

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

    • (e)是偶数时:

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

    ( ext{lose}(s,e))

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

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

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

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

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

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

    时间复杂度(O(tlog 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;
    }
    
  • 相关阅读:
    md5编码的两个程序
    DotNetNuke 5 User's Guide Get Your Website Up and Running读书摘录3
    纪念今天DNN密码破解
    DotNetNuke 5 User's Guide Get Your Website Up and Running读书摘录4
    文件与目录的默认权限与隐藏权限(转)
    EXT2 文件系统
    磁盘与目录的容量(转)
    文件的搜寻(转)
    权限与命令间的关系(转)
    账户切换(转)
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13187407.html
Copyright © 2011-2022 走看看