感谢繁凡さん大佬的指点,他的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} hi≥hi+1就能滚向下一座山,否则就会让当前山的高度加一,问第 k k k次滚动的小球在哪(滚出了最后一座山就输出-1)。
简单的模拟题,因为 n ≤ 100 n\le 100 n≤100,最坏情况山的高度按照1,2,3…100排列,也能在1e4内完成模拟。因为 k ≤ 1 , 000 , 000 , 000 k\le 1,000,000,000 k≤1,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) (x→y)=(y→x),有的话直接构造;(ii)当 k ≠ 2 k\ne 2 k=2时,一定在三元环中能找到两个结点满足 ( x → y ) = ( y → z ) (x\rightarrow y)=(y\rightarrow z) (x→y)=(y→z),然后按照 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 i−1(下标从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(n∗n).
但是如果要恰好装满背包,这个可能无解。我们找到能够选择到的小于 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';
}
}
}