Codeforces Round #699 (Div. 2)

  感谢繁凡さん大佬的指点,他的cf博客比我的详细,可以移步关注了解一下。


A:Space Navigation

  题目大意是给定你一个代表移动的序列,你只能去掉某些轮次的移动(去掉某些字母),不能改变序列的次序,问能不能到达终点。
  就直接统计四个方向的次数,例如终点的X坐标为正只要满足向右移动的次数不小于X,X坐标为负只要满足向左移动的次数不小于X的绝对值即可。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int main()
{
    close;int T;cin>>T;
    while(T--)
    {
        int x,y;cin>>x>>y;
        string s;cin>>s;
        int numu=0,numd=0,numl=0,numr=0,len=s.length();
        for(int i=0;i<len;++i) 
            if(s[i]=='R') numr++;
            else if(s[i]=='L') numl++;
            else if(s[i]=='U') numu++;
            else if(s[i]=='D') numd++;
        if(((x>=0 && numr>=x)||(x<0 && numl>=-x))&&((y>=0 && numu>=y)||(y<0 && numd>=-y))) cout<<"YES\n";
        else cout<<"NO\n";
    }
}

B:New Colony

  题目是想模拟一个小球在山峰上的滚动,如果 h i ≥ h i + 1 h_i\ge h_{i+1} hihi+1就能滚向下一座山,否则就会让当前山的高度加一,问第 k k k次滚动的小球在哪(滚出了最后一座山就输出-1)。
  简单的模拟题,因为 n ≤ 100 n\le 100 n100,最坏情况山的高度按照1,2,3…100排列,也能在1e4内完成模拟。因为 k ≤ 1 , 000 , 000 , 000 k\le 1,000,000,000 k1,000,000,000,所以要在小球滚出最后一座山后及时终止模拟,否则TLE。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int h[200];
int main()
{
    close;int T;cin>>T;
    while(T--)
    {
        int n,k;cin>>n>>k;
        for(int i=1;i<=n;++i) cin>>h[i];
        int num=0,pos;h[n+1]=0;
        while(num<k)
        {
            pos=n+1;
            for(int i=1;i<=n;++i){
                if(h[i]<h[i+1]) {h[i]++;pos=i;break;}
            }
            if(pos==n+1) break;
            num++;
        }
        if(num>=k) cout<<pos<<'\n';
        else cout<<"-1\n";
    }
}

C:Fence Painting

  题目大意是给定 n n n个栅栏原先的颜色和想变成的颜色,然后会按照顺序来 m m m个粉刷匠,每个粉刷匠都有一种颜色,你必须选择一个栅栏让他把这个栅栏变成他所携带的颜色,问最后能不能满足我们的目的。
  一个比较大的模拟,关键在于如何处理不需要的粉刷匠。①一个粉刷匠如果携带的颜色正好能满足我们的需要,直接刷;②一个粉刷匠携带的颜色如果我们不需要,但有栅栏是这个颜色,就可以重复刷;③如果一个粉刷匠携带的颜色我们不需要,而且栅栏没有这个颜色,先暂存到一个队列,如果后面有①②操作,就把队列里的都往①②操作所选择的栅栏上刷。失败条件就是:没有完全修改/队列不为空。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e5+100;
int a[maxn],b[maxn],c[maxn],rec[maxn],p[maxn];
vector<int> change[maxn],stay[maxn];
int main()
{
    close;int T;cin>>T;
    while(T--)
    {
        memset(p,0,sizeof(p));
        int n,m,num=0;cin>>n>>m;
        for(int i=1;i<=n;++i) change[i].clear(),stay[i].clear();
        for(int i=1;i<=n;++i) cin>>a[i];
        for(int i=1;i<=n;++i){
            cin>>b[i];
            if(b[i]!=a[i]) num++,change[b[i]].push_back(i);
            else stay[b[i]].push_back(i);
        }
        for(int i=1;i<=m;++i) cin>>c[i];
        if(m<num) {cout<<"NO\n";continue;}
        queue<int> tmp;while(!tmp.empty()) tmp.pop();
        for(int i=1;i<=m;++i){
            if(change[c[i]].size()==0){
                if(stay[c[i]].size()!=0){
                    rec[i]=stay[c[i]][0];
                    if(!tmp.empty()){
                        while(!tmp.empty()){int cur=tmp.front();tmp.pop();rec[cur]=rec[i];}
                    }
                } 
                else tmp.push(i);
            }
            else{
                if(p[c[i]]<change[c[i]].size()) rec[i]=change[c[i]][p[c[i]]],p[c[i]]++,num--;
                else rec[i]=change[c[i]][0];
                if(!tmp.empty()){
                    while(!tmp.empty()){int cur=tmp.front();tmp.pop();rec[cur]=rec[i];}
                }
            }
        }
        if(num!=0 || !tmp.empty()) cout<<"NO\n";
        else{
            cout<<"YES\n"<<rec[1];
            for(int i=2;i<=m;++i) cout<<' '<<rec[i];
            cout<<"\n";
        }
    }
}

D:AB Graph

  题目大意是给定一张有向完全简单图,每条边都有一个标号:a/b,你能不能构造一个行走的顺序,使得走出来的路径是一个长度为 k k k的回文串。
  构造题。①如果 k k k是奇数,直接找一个二元环走奇数步一定是回文串;②如果 k k k是偶数:(i)找到有没有两个结点满足 ( x → y ) = ( y → x ) (x\rightarrow y)=(y\rightarrow x) (xy)=(yx),有的话直接构造;(ii)当 k ≠ 2 k\ne 2 k=2时,一定在三元环中能找到两个结点满足 ( x → y ) = ( y → z ) (x\rightarrow y)=(y\rightarrow z) (xy)=(yz),然后按照 k 2 \frac{k}{2} 2k是不是奇数构造即可。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1100;
string s[maxn];int n,k;
void solve()
{
	if(k&1){
		cout<<"YES\n"<<2;
		for(int i=1;i<=k;++i) cout<<' '<<((i&1)?1:2);
		cout<<"\n";return;
	}
	for(int i=0;i<n;++i)
		for(int j=i+1;j<n;++j)
			if(s[i][j]==s[j][i]){
				cout<<"YES\n"<<i+1;
				for(int num=1;num<=k;++num) cout<<' '<<((num&1)?j+1:i+1);
				cout<<"\n";return;
			}
	if(n==2){cout<<"NO\n";return;}
	cout<<"YES\n";
	int A,B,C;
	if(s[0][1]==s[1][2]) A=1,B=2,C=3;
	else if(s[1][2]==s[2][0]) A=2,B=3,C=1;
	else A=3,B=1,C=2;
	if((k/2)&1){
		cout<<A<<' '<<B<<' '<<C;
		for(int i=1;i<=(k/4);++i) cout<<' '<<B<<' '<<A<<' '<<B<<' '<<C;
		cout<<"\n";
	}
	else{
		cout<<B;
		for(int i=1;i<=(k/4);++i) cout<<' '<<A<<' '<<B<<' '<<C<<' '<<B;
		cout<<"\n";
	}
	return;
}
int main()
{
    close;int T;cin>>T;
	while(T--)
	{
		cin>>n>>k;getline(cin,s[0]);
		for(int i=0;i<n;++i)
			getline(cin,s[i]);
		solve();
	}
}

E:Sorting Books

  题目大意是书架上放有 n n n本书,每一次操作你都可以选择一本书放到最末尾,问最少需要多少次操作就可以让 n n n本书中相同种类的书都相邻。
  转化一下问题,我们可以选择一个区间中的若干本书不动,剩下的书我们一定可以按照一个顺序将其放在队列最后满足条件。因此,我们可以令 d p [ i ] dp[i] dp[i]表示区间 [ i . . . n ] [i...n] [i...n]中不需要动的书最多有多少本, l [ k ] , r [ k ] l[k],r[k] l[k],r[k]表示种类为 k k k的书出现的左右端点, c n t [ k ] cnt[k] cnt[k]表示种类为 k k k的书在区间 [ i . . . n ] [i...n] [i...n]的出现次数。可以得到转移方程:
①当 i ≠ l [ a [ i ] ] i\ne l[a[i]] i=l[a[i]]时, d p [ i ] = m a x ( d p [ i + 1 ] , c n t [ a [ i ] ] dp[i]=max(dp[i+1],cnt[a[i]] dp[i]=max(dp[i+1],cnt[a[i]];
②当 i = l [ a [ i ] ] i=l[a[i]] i=l[a[i]]时, d p [ i ] = m a x ( d p [ i + 1 ] , c n t [ a [ i ] ] + d p [ r [ a [ i ] + 1 ] ) dp[i]=max(dp[i+1],cnt[a[i]]+dp[r[a[i]+1]) dp[i]=max(dp[i+1],cnt[a[i]]+dp[r[a[i]+1]).
  说明一下为什么要进行这样的转移。对于当前的第 i i i本书来说,我们肯定可以把这本书直接放到队列最末尾,这样就有 d p [ i ] = d p [ i + 1 ] dp[i]=dp[i+1] dp[i]=dp[i+1];而如果种类 a [ i ] a[i] a[i]的这本书是出现的最左端,我们才可以用②转移,原因是这个区间如果没有完整出现,那么我们只能把这个区间所有的除了第 a [ i ] a[i] a[i]类书都拿走,才能保证未出现的 a [ i ] a[i] a[i]类书还能通过移到队列最尾与当前相邻。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=5e5+100;
int l[maxn],r[maxn],dp[maxn],cnt[maxn],a[maxn];
int main()
{
	close;int n;cin>>n;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];
		if(l[a[i]]==0) l[a[i]]=i;
		r[a[i]]=i;
	}
	dp[n]=1,cnt[a[n]]++;
	for(int i=n-1;i>0;--i)
	{
		if(i==l[a[i]]) dp[i]=max(dp[i+1],++cnt[a[i]]+dp[r[a[i]]+1]);
		else dp[i]=max(dp[i+1],++cnt[a[i]]);
	}
	cout<<n-dp[1];
}

F:AB Tree

  题目大意是给定一棵根节点为1的树,你需要给每个结点赋值:‘a’/‘b’(但只能有 x x x个’a’结点),然后从根节点出发,到达所有结点的路径会得到很多字符串,问怎么样赋值才能使得到的字符串的种类数最小?
  (感谢繁凡さん的博客)首先我们DFS跑一遍树,我们会得到所有结点的深度depth,那我们得到的字符串的长度就是1~depth。很明显,现在我们已经得到了depth种不同的字符串(长度不同的字符串必定不同)。
  然后我们使用贪心的策略:如果某一层 i i i结点上的字符都相同,那么所有能得到的字符串下标为 i − 1 i-1 i1(下标从0开始)的地方都是相同的。进而我们可以得出一个结论:如果我们能选择某些层,让这些层的结点数恰好等于 x x x,那么我们就能够得到仅仅长度不同的depth个字符串。
  把每一层的结点数抽象成一个物品的体积, x x x就是背包的体积,那么这个问题就演变成了多重背包的可行性问题,这个做法可以用bitset去优化。而且我们知道一棵树最多有 n \sqrt n n 种每层不同的结点数(假设每层结点是1,2,… n \sqrt n n ),这样的做法的时间复杂度是 O ( n ∗ n ) O(n*\sqrt n) O(nn ).
  但是如果要恰好装满背包,这个可能无解。我们找到能够选择到的小于 x x x的最大的结点个数,剩下的点我们通过改变一些b来实现。如何改变能够使得对结果的影响最小?那就是改变叶子结点的值。我们要找到原本一层都是b都是结点,然后这一层的叶子结点最多(我们要去修改一层的叶子结点,这样仅仅会造成答案变为depth+1),直接修改即可。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e5+100;
int n,x,maxnum=0,maxdep=0,object_num=0,val[maxn],judge[maxn],deep[maxn],leaf[maxn];
vector<int> G[maxn],depth[maxn],cnt[maxn];
bitset<maxn> b[2100];
void DFS(int root,int dep)
{
	if(dep>maxdep) maxdep=dep;
	deep[root]=dep;
	depth[dep].push_back(root);
	if(G[root].size()==0) leaf[dep]++;
	for(auto cur:G[root]) DFS(cur,dep+1);
}
void Find(int x)
{
	for(int i=object_num;i>0;--i)
	{
		int size=cnt[val[i]].size();
		for(int j=0;j<size;++j){
			if(val[i]>x || b[i-1][x]) break;
			x-=val[i],judge[cnt[val[i]][j]]=1;
		}
	}
}
int main()
{
	close;cin>>n>>x;
	for(int i=2;i<=n;++i){int fa;cin>>fa;G[fa].push_back(i);}
	DFS(1,1);
	for(int i=1;i<=x;++i){
		int size=depth[i].size();
		if(size>maxnum) maxnum=size;
		cnt[size].push_back(i);
	}
	b[object_num][0]=1;
	for(int i=1;i<=maxnum;++i){
		int size=cnt[i].size();
		if(size==0) continue;
		val[++object_num]=i;
		b[object_num]=b[object_num-1];
		for(int j=1;j<=size;j<<=1){
			size-=j;
			b[object_num]|=(b[object_num]<<(i*j));
		}
		if(size>0) b[object_num]|=(b[object_num]<<(i*size));
	}
	if(b[object_num][x]){
		cout<<maxdep<<endl;
		Find(x);
		for(int i=1;i<=n;++i) cout<<(judge[deep[i]]?'a':'b');
	}
	else{
		int res;
		for(int i=x;i>=0;--i){if(b[object_num][i]) {res=i;break;}}
		Find(res);int pos;res=x-res;
		for(int i=1;i<=maxdep;++i)
			if(!judge[i] && leaf[i]>=res) {pos=i;break;}
		cout<<maxdep+1<<endl;
		for(int i=1;i<=n;++i){
			if(judge[deep[i]]) cout<<'a';
			else if(deep[i]==pos && res>0 && G[i].size()==0) cout<<'a',res--;
			else cout<<'b';
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值