Codeforces Round #554 ( div.2 ) 总结

应该经常需要锻炼一下英语阅读理解能力和代码能力,所以以后还是需要多打打CF。

今天大概就是水一水找找感觉。

A. Neko Finds Grapes

$n$个箱子,$m$个钥匙 ($n,m leq 10^6$),每个箱子有参数$a_i$,每个钥匙有参数$b_i$

当且仅当,$a_i + b_j equiv 1 (mod 2)$,则说箱子和钥匙配对成功。

注意到,一个箱子只能和一个钥匙配对最多一次。

要求最大化配对数。

Solution : 直接统计即可,考虑配对只可能是奇数+偶数或者偶数+奇数。

所以答案必然是$min{ cnta_{奇} , cntb_{偶}} +min{ cnta_{偶} , cntb_{奇}}  $

# include <bits/stdc++.h>
using namespace std;
int cntA[2],cntB[2];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) {
        int t; scanf("%d",&t);
        cntA[t%2]++;
    } 
    for (int i=1;i<=m;i++) {
        int t; scanf("%d",&t);
        cntB[t%2]++;
    }
    int ans=min(cntA[1],cntB[0])+min(cntA[0],cntB[1]);
    printf("%d
",ans);
    return 0;
}
A.cpp

B. Neko Performs Cat Furrier Transform

定义在一个数$x(xleq 10^6)$上的A,B两种操作,

A操作:$x=x igoplus  (2^n-1)$(其中$n$的值可以自己选定)

B操作:$x=x+1$ 

要求A,B两个操作交替进行,要求输出方案将$x$的最终值变成$2^m-1$即可。

要求步骤数不超过40步,但是不要求最短。

Solution:事实上A操作的意图是将数字的末若干位取反。B操作含义是将数最后一位+1,并且进位。

考虑一个贪心,如果每次取将第一个0出现的位置往低位取反,然后最后在末尾+1,那么每次至少消去第一个0出现的位置的一个0

所以必然2步就可以至少删去1个0,那么显然可以在40步内完成任务。

  本题主要需要注意,操作步数不一定是偶数,可能最后的B操作不做,操作步数是奇数的,需要特判。

# include <bits/stdc++.h>
using namespace std;
int base[25];
int ttt[100];
int getbit(int x)
{
    int t=x,cnt=0;
    while (t) t/=2,cnt++;
    return cnt;
}
bool check(int x)
{
    for (int i=0;i<=20;i++)
     if (x==base[i]) return 1;
    return 0;
}
int main()
{
    int uuu=0;
    int x,ans=0; scanf("%d",&x);
    for (int i=0;i<=20;i++) base[i]=(1<<i)-1;
    if (check(x)) {
        puts("0"); return 0;
    }
    while (true) {
        int p=getbit(x);
        bool flag=false;
        for (int i=p-2;i>=0;i--)
         if (((1<<i)&x)==0) {
             x=x^((1<<(i+1))-1); ttt[++ttt[0]]=i+1; uuu++;
            if (check(x)) flag=true; 
            if (flag) break;
            x=x+1; uuu++;
             if (check(x)) flag=true; 
            if (flag) break;
             break;
         }
        if (flag) break;
    }
    printf("%d
",uuu);
    for (int i=1;i<=ttt[0];i++)
     printf("%d ",ttt[i]);
    return 0;
}
B.cpp

 C. Neko does Maths

给出数$a,b(a,bleq 10^9)$,要求先最小化$lcm(a+k,b+k)$的同时,最小化$k$的值。

Solution:不妨设$a>b$ 

显然$lcm(a+k,b+k)=frac{(a+k)(b+k)}{gcd(a+k,b+k)}$

由辗转相减法可知$gcd(a,b)=gcd(b,a-b)$,

所以原式可化为$lcm(a+k,b+k)=frac{(a+k)(b+k)}{gcd(b+k,a-b)}$

对于$a-b$的值是定值,那么我们就可以枚举$a-b$的因子$w$然后反过来O(1)求出最小的$k$就行了。

若$b \%  w = 0$ 那么 $k = 0$ 否则 $k = (left lfloor frac{b}{w} ight floor + 1)w-b$

复杂度$ O(sqrt{a-b}) $

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N=1e5+10;
int n,A[N];
signed main()
{
    int a,b; scanf("%lld%lld",&a,&b);
    if (a<b) swap(a,b); int d=a-b;
    for (int i=1;i<=sqrt(d);i++)
     if (d%i==0) A[++n]=i,A[++n]=d/i;
    int ans=0x7fffffffffffffff,pr=0; 
    for (int i=1;i<=n;i++) {
        int w=A[i],k=(b%w)?(b/w+1)*w-b:0ll;
        if (ans>(a+k)*(b+k)/w) ans=(a+k)*(b+k)/w,pr=k;
    } 
    cout<<pr<<'
';
    return 0;
}
C.cpp

 D. Neko and Aki's Prank

考虑一个长度为2n ($n leq 1000$)的括号序列所在的字典树,一组合法的选边是任取一条边两端的两个点都不在边集中其他边中出现。

最大化选边集合的边数。 由于答案较大,对$10^9 + 7 $取模.

Solution : 

考虑到括号序列所在的trie树,若两个括号序列前缀平衡数一定的时候,那么为了保证最后得到合法的括号序列,那么显然,其子树必然相等。

显然,我们不需要重复计算这些冗余的量。

考虑维护这个状态 , 考虑到确定一个等价的状态只需要确定当前序列总长度和当前平衡值即可。

我们这里对于当前括号序列平衡值的定义是:当前左括号数-右括号数。

显然,通过这两种状态就可以确定一些相互等价的点。这些状态的数目是$n^2$

考虑一个简单的dp,设$f[i][j]$表示当前括号序列长度为$i$(深度),左括号数-右括号数(平衡值)

考虑转移:

$ f[i][j] += f[i-1][j-1] $新增1个右括号
$ f[i][j] += f[i-1][j+1] $新增1个左括号

我们需要知道转移是否合法,即能否组成新的边,这取决于父亲节点$(i-1)$的合法性。

不妨设$g[i][j]$为$f[i][j]$意义下对于$(i,j)$的合法性 ,转移也非常显然 $g[i][j] = !(g[i-1][j-1]||g[i-1][j+1])$

所以若$ g[i][j] = 1$ 时 生成(i,j) 的方案会额外多1种。

所以转移方程可以写成

$ f[i][j] = f[i-1][j-1] + f[i-1][j+1] + g[i-1][j-1]||g[i-1][j+1]$
$ g[i][j] = !(g[i-1][j-1]||g[i-1][j+1])$

边界 $f[0][0]=0, g[0][0] = 1$

细节方面直接对于一个$ f[i][j] $显然需要满足$j leq i$。

# include <bits/stdc++.h>
# define int long long
const int N=2e3+10;
const int mo=1e9+7;
int f[N][N]; bool g[N][N];
using namespace std;
signed main()
{
    int n; scanf("%lld",&n); n<<=1;
    f[0][0]=0,g[0][0]=true;
    for (int i=1;i<=n;i++)
     for (int j=0;j<=n;j++)
      {
          int ret=0,flag=0;
          if (j!=0) ret=(ret+f[i-1][j-1])%mo,flag|=g[i-1][j-1];
          if (j+1<=i-1) ret=(ret+f[i-1][j+1])%mo,flag|=g[i-1][j+1];
          if (flag) f[i][j]=(ret+1)%mo,g[i][j]=false;
          else f[i][j]=ret%mo,g[i][j]=true;
      }
    printf("%lld
",f[n][0]);  
    return 0;
}
D.cpp

E. Neko and Flashback

若有一个已知的$a_n$数组,对于$i in [1,n-1]$ 定义$b_n , c_n $

即$b_i = min(a_i,a_{i+1}) , c_i=max(a_i,a_{i+1})$ 

现在给出的是乱序的$b,c$数组,询问是否能求出$a$数组,若能输出方案否则输出"-1"

Solution : 显然对于$b,c$两个数组所描述的数一定是相邻两个数。

也就是说给出这两个数组的目的是告诉选手那两个数是被放在相邻位置的。

对于第$i$组要求,表示数$b_i$和数$c_i$出于相邻位置,于是我们将其建一条无向边。

显然如果构成一组合法的序列,那么这些限制都需要满足,自然想到了欧拉路径(不重复经过所有边)

现在,这条路径上点表示的数就是所求的序列。

于是,我们只需要将点权离散化Fleury跑欧拉路径即可,复杂度大概是O(E)

这里只有$n-1$条边,所以总复杂度是$O(n)$

注意,图可能不是欧拉图,甚至不连通,甚至输入就不合法($a_i > b_i$)在这些情况下直接输出-1即可。

# include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int du[N],a[N],b[N],t[N],Path[N],T,n;
vector<int>V[N];
int Ls(int x){return lower_bound(t+1,t+1+T,x)-t;}
int Rl(int x){return t[x];}
void adde(int u,int v)
{
    //printf("adde : %d %d
",u,v);
    V[u].push_back(v);
    V[v].push_back(u);
    du[u]++; du[v]++;
}
void del(int u,int v)
{
    //printf("del %d %d
",u,v);
    for (int i=0;i<V[u].size();++i)
     if (V[u][i]==v) {
         V[u].erase(V[u].begin()+i);
         break;
     }
    for (int i=0;i<V[v].size();++i)
     if (V[v][i]==u) {
         V[v].erase(V[v].begin()+i);
         break;
     }
}
void dfs(int u)
{
    //printf("u = %d 
",u);
    for (int i=0;i<V[u].size();++i) {
        int v=V[u][i]; del(u,v); dfs(v);
    } 
    Path[++Path[0]]=u;
}
int main()
{
    scanf("%d",&n); 
    for (int i=1;i<=n-1;i++) scanf("%d",&a[i]),t[++t[0]]=a[i];
    for (int i=1;i<=n-1;i++) scanf("%d",&b[i]),t[++t[0]]=b[i];
    for (int i=1;i<=n-1;i++) 
        if (a[i]>b[i])  { puts("-1"); return 0;}    
    sort(t+1,t+1+t[0]);
    T=unique(t+1,t+1+t[0])-t-1;
    for (int i=1;i<=n-1;i++)
     adde(Ls(a[i]),Ls(b[i]));
    bool flag=false; int cnt=0;
    for (int i=1;i<=n;i++)
     if (du[i]%2==1) cnt++;
    if (cnt!=0&&cnt!=2) { puts("-1"); return 0;}
    for (int i=1;i<=n;i++)
     if (du[i]%2==1) { flag=true; dfs(i); break;}
    if (flag==false) dfs(1);
    if (Path[0]!=n) { puts("-1"); return 0;    }  
    for (int i=1;i<=Path[0];i++) 
     printf("%d ",Rl(Path[i]));
    puts("");  
    return 0;
}
E.cpp
原文地址:https://www.cnblogs.com/ljc20020730/p/10766001.html