赛时写了前 5 5 5 题,拿了个五十几的排名当时沾沾自喜,然后重测时 T3,T4 数组开小同时爆炸了……
只需要找到 a , b a,b a,b 数组的一个相同元素即可。
(大概只有我这种菜逼才会在这题WA一发吧……)
代码如下:
#include
#include
int T,n,m;
bool v[1010];
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d %d",&n,&m);
memset(v,false,sizeof(v));
for(int i=1,x;i<=n;i++)scanf("%d",&x),v[x]=true;
int ans=-1;
for(int i=1,x;i<=m;i++){
scanf("%d",&x);
if(v[x])ans=x;
}
if(ans!=-1)printf("Yes\n1 %d\n",ans);
else printf("No\n");
}
}
可以发现,答案其实只取决于开头有多少个 1 1 1。
假设去掉这段 1 1 1,对于一个不为 0 0 0 的数 a i a_i ai,此时的先手可以有两种操作:1、取 a i − 1 a_i-1 ai−1 个;2、取 a i a_i ai 个,通过这两种操作,可以完全控制后手的操作。
控制后手操作分两种情况:
当 a i + 1 > 1 a_{i+1}>1 ai+1>1 时,先手取 a i − 1 a_i-1 ai−1 个,后手只能取 1 1 1 个,那么 a i + 1 a_{i+1} ai+1 的操作权依然在先手手里。
假如 a i a_i ai 的后面是一段 1 1 1,那么先手可以通过上面两种操作决定这段 1 1 1 的最后一个 1 1 1 的操作权在谁手里,或者说,是这段 1 1 1 后面的那个 > 1 >1 >1 的 a j a_j aj 的操作权在谁手里。
所以代码如下:
#include
int T,n,a[1000010];
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d",&n);
int ans=1;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<n;i++)if(a[i]==1)ans^=1;else break;
if(ans)printf("First\n");
else printf("Second\n");
}
}
因为每次一定要翻转一段前缀,容易想到从后往前构造,即从后往前让 a a a 与 b b b 逐渐相同。
假设此时操作到第 i i i 位, i + 1 i+1 i+1 ~ n n n 位都已经相等了。
假如 a i = b i a_i=b_i ai=bi,那么无事发生,跳过。
假如 a i ≠ b i a_i\neq b_i ai=bi,那么肯定要翻转一次 1 1 1 ~ i i i 这个前缀,翻转后 a 1 a_1 a1 取代 a i a_i ai。
那么此时看一看, a 1 a_1 a1 取反之后是否等于 b i b_i bi,假如不是,那么就先翻转 1 1 1 ~ 1 1 1,这样可以将 a 1 a_1 a1 取反,然后再翻转 1 1 1 ~ i i i 即可。
时间复杂度 O ( n 2 ) O(n^2) O(n2),最大操作次数不超过 3 n 3n 3n,可以通过。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 2010
int T,n,ans[maxn],t;
char S1[maxn],S2[maxn];
void sel(int x)
{
ans[++t]=x;
for(int i=1;i<=x;i++)
if(S1[i]=='1')S1[i]='0';else S1[i]='1';
for(int i=1;i<=x/2;i++)swap(S1[i],S1[x-i+1]);
}
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d",&n);t=0;
scanf("%s %s",S1+1,S2+1);
for(int i=n;i>=1;i--)
if(S1[i]==S2[i])continue;
else{
if(S1[1]==S2[i])sel(1);
sel(i);
}
printf("%d ",t);for(int i=1;i<=t;i++)printf("%d ",ans[i]);printf("\n");
}
}
当时发现上面那个构造方法的操作次数也恰好并没有超过 2 n 2n 2n,于是就想着直接拿来用。
发现翻转这个过程可以优化,可以用一个链表来维护前缀,那么翻转就相当于交换首尾指针,这样可以做到 O ( n ) O(n) O(n)。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 200010
int T,n,ans[maxn],t;
char S1[maxn],S2[maxn];
struct node{int x;node *next,*last;}*s[maxn];
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d",&n);t=0;
scanf("%s %s",S1+1,S2+1);
for(int i=1;i<=n;i++)s[i]=new node();
s[0]=s[n+1]=NULL;
for(int i=1;i<=n;i++)s[i]->x=S1[i]-'0',s[i]->next=s[i+1],s[i]->last=s[i-1];
node *st=s[1],*ed=s[n];int now=0;
for(int i=n;i>=1;i--)
if((ed->x^now)==S2[i]-'0')
{
if(i==1)continue;
if(!ed->next)ed=ed->last,ed->next=NULL;
else ed=ed->next,ed->last=NULL;
}
else
{
if((st->x^now)==S2[i]-'0')ans[++t]=1,st->x^=1;
ans[++t]=i;swap(st,ed);now^=1;
if(i==1)continue;
if(!ed->next)ed=ed->last,ed->next=NULL;
else ed=ed->next,ed->last=NULL;
}
printf("%d ",t);for(int i=1;i<=t;i++)printf("%d ",ans[i]);printf("\n");
}
}
容易发现一个性质,对于一个 i , j i,j i,j,假如满足 p i < p j p_i
那么我们就可以将 p p p 分成若干段,看看能不能将这些段分成两堆,每堆大小为 n n n 即可,可以做一下背包。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 2010
int T,n,s[maxn],t,f[maxn];
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d",&n);
int last;t=0;
for(int i=1,x,tot=0;i<=(n<<1);i++){
scanf("%d",&x);
if(i==1)last=x,tot++;
else{
if(last>x)tot++;
else s[++t]=tot,last=x,tot=1;
}
}
memset(f,0,sizeof(f));
f[0]=1;
for(int i=1;i<=t;i++)
for(int j=n;j>=s[i];j--)
f[j]|=f[j-s[i]];
if(f[n])printf("YES\n");
else printf("NO\n");
}
}
当时想出了大概的思路,但是由于当时思路不清晰加上各种菜,所以当时并没有写出来,并且后来发现自己的思路有一个小地方是锅了的,所以这里讲讲题解做法。
首先设 o t h oth oth 为整个序列中没有出现过的颜色,显然存在这样的一个颜色。
假设已经选定了哪 x x x 个位置不动,设 i d x idx idx 表示剩下 n − x n-x n−x 个位置中出现次数最多的颜色的出现次数,那么无论如何交换颜色,至少会有 2 × i d x − ( n − x ) 2\times idx-(n-x) 2×idx−(n−x) 个位置依然保持原来的颜色。
但是我们要求剩下的 n − x n-x n−x 个位置不能有和原来相同的颜色,所以要将那些相同的位置替换为 o t h oth oth,我们需要进行 n − y n-y n−y 次,假如换完还有相同就无解了,即 2 × i d x − ( n − x ) > n − y 2\times idx-(n-x)>n-y 2×idx−(n−x)>n−y 则无解。
有解的话,将剩下的 n − x n-x n−x 个颜色排个序,然后将颜色整体左移 ⌊ n − x 2 ⌋ \lfloor \dfrac {n-x} 2 \rfloor ⌊2n−x⌋ 位即可,相同的强制换成 o t h oth oth,这样就做完了。
我之前的做法和题解不一样的就是最后的答案构造,我的做法很蠢,我是保留出现次数最少的 y − x y-x y−x 个颜色,然后在后面放 n − y n-y n−y 个 o t h oth oth,然后整体左移,这样的问题在于, o t h oth oth 的位置不一定是连续的,可能在那 y − x y-x y−x 个的中间。
代码如下:
#include
#include
#include
#include
using namespace std;
#define maxn 100010
int T,n,x,y,a[maxn];
vector<int>col[maxn],tot[maxn],s;
int ans[maxn];
bool v[maxn];
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d %d %d",&n,&x,&y);
for(int i=0;i<=n+1;i++)col[i].clear(),tot[i].clear(),ans[i]=0,v[i]=false;
for(int i=1;i<=n;i++)scanf("%d",&a[i]),col[a[i]].push_back(i);
for(int i=1;i<=n+1;i++)tot[col[i].size()].push_back(i);
int idx=n;for(int i=1;i<=x;i++){
while(!tot[idx].size())idx--;
int z=tot[idx].back();
ans[col[z].back()]=z;
col[z].pop_back();
tot[idx].pop_back();
tot[idx-1].push_back(z);
}
while(!tot[idx].size())idx--;
if(idx*2-(n-x)>n-y){printf("NO\n");continue;}
s.clear();for(int i=1;i<=idx;i++)
for(int j=0;j<tot[i].size();j++)s.insert(s.end(),col[tot[i][j]].begin(),col[tot[i][j]].end());
int ch_=n-y;
#define ch(x) ans[x]=tot[0][0],ch_--,v[x]=true
for(int i=0;i<n-x;i++){
ans[s[i]]=a[s[(i+(n-x)/2)%(n-x)]];
if(ans[s[i]]==a[s[i]])ch(s[i]);
}
for(int i=0;ch_;i++)if(!v[s[i]])ch(s[i]);
printf("YES\n");
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
printf("\n");
}
}