Codeforces Round #520 (Div. 2)解题报告

A. A Prank


题意: 给你长度为n的数组,元素满足条件 ( 1 ≤ a 1 &lt; a 2 &lt; ⋯ &lt; a n ≤ 1 0 3 ) (1≤a_1&lt;a_2&lt;⋯&lt;a_n≤10^3) (1a1<a2<<an103)。 现在问你最多能隐藏多少个连续的元素,使得:通过剩下的所有元素可以复原整个数组。

题解: 首先注意,最小的元素只能是1,最大的元素只能是1000,所以如果给你1 2 3 4,把前三个元素隐藏也是可以还原成原数组的。
所以在开头放一个0,结尾放一个1001,找到最长的连续的数字,长度-2就是答案。注意没有连续的数字的情况。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int num[105];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);
    }
    num[0]=0;num[n+1]=1001;
    int flag=0;
    for(int i=n;i>=0;i--){
        for(int j=0;j+i+1<=n+1;j++){
            if(num[j+i+1]-num[j]==i+1){
                printf("%d\n",i);
                flag=1;
                break;
            }
        }
        if(flag==1){
            break;
        }
    }
    if(flag==0){
        printf("0\n");
    }
    return 0;
}

B. Math


题意: 给你一个正整数n ( 1 ≤ n ≤ 1 0 6 ) (1≤n≤10^6) (1n106),你有两种操作:

  1. n乘上任意正整数x,即 n = n ∗ x , x &gt; 0 n=n*x,x&gt;0 n=nx,x>0
  2. 对n取算术平方根,即 n = s q r t ( n ) n=sqrt(n) n=sqrt(n),但n开根号之后一定要是一个整数否则不能做这个操作。

现在问你n能够变成的最小数字是什么,输出这个数字,并且输出到达这个数字的最少操作次数。

题解: 很明显,如果n含有一个质数p,n不管怎么变化至少会含有一个这个p(因为不能对一个单独的质数开根号)。我们对n进行质因数分解,可得: n = ∏ p i j i n=\prod p_i^{j_i} n=piji,所以如果 p i p_i pi的次方,刚好是2的整数次幂,我们开根号即可。否则我们就可以乘上一个数字让它变成大于当前次幂的最小的2的整数次幂。最大的这个次幂数就决定了所有质数,被开根号的次数(因为次幂小的那些质数也要跟着一起开根号)
例如:
n = 2 3 ∗ 3 6 n=2^3*3^6 n=2336,那么我们必须要把 3 6 3^6 36变成 3 8 3^8 38才能让3最后只剩下 1次方。那么就至少要开根号3次,所以对于质数2我们也要把它变成8次方。

另外对于所有的 p i p_i pi我们最多只要乘上一次即可让所有 p i p_i pi变成次幂是2的整数次幂,所以乘法要么有0次,要么有1次。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int prime[1000000+10],cnt=0,vis[1000000+10],num[1000000+10],ned[1000000+10];
void init()
{
    for(int i=2;i<=1000000;i++){
        if(vis[i]==0){
            prime[cnt++]=i;
        }
        for(int j=0;j<cnt&&prime[j]<=1000000/i;j++){
            vis[prime[j]*i]=1;
            if(i%prime[j]==0) break;
        }
    }
}
int main()
{
    init();
    //printf("%d\n",cnt);
    int n,ans=0;
    scanf("%d",&n);
    int flag=0;
    int ans1=1;
    int v=n;
    for(int i=0;i<cnt;i++){
        if(v%prime[i]==0) ans1*=prime[i];
        while(v%prime[i]==0){
            num[i]++;
            v/=prime[i];
        }
        if(num[i]==0) continue;

        int x=1,tmp=0;
        while(x<num[i]){
            x<<=1;
            tmp++;
        }
        if(x>num[i]) flag=1;
        ned[i]=tmp;
        ans=max(ans,tmp);
    }
    for(int i=0;i<cnt;i++){
        if(num[i]&&ned[i]<ans){
            flag=1;
        }
    }
    //printf("## %d\n",flag);
    printf("%d ",ans1);
    if(flag==0){
        printf("%d\n",ans);
    }
    else{
        printf("%d\n",ans+1);
    }
    return 0;
}

C. Banh-mi


题意: 给你一个正整数n和一个正整数q,再给你一个长度为n的数组(由于一开始只有0和1,所以实际上是给你一个01串),q代表询问的个数,每次询问给你一个区间 [ l , r ] [l,r] [l,r]
每当你取一个数字,该区间里剩下的所有没有取的数字都会加上当前数字的价值,
对于每次询问,要你求出这个区间里能获得的最大价值。询问与询问之间是独立互不影响的。

题解: 核心是找规律。很明显,先取原来是1的地方的数字,原来是1的地方取光之后再去取原来是0的地方的数字。

那么试一下就会发现答案:
l e n = r − l + 1 len=r-l+1 len=rl+1
p r e [ i ] pre[i] pre[i]表示从 0 0 0 i i i有多少个地方原来是 1 1 1
a n s = ( 2 p r e [ r ] − p r e [ l − 1 ] − 1 ) ∗ ( 2 l e n − ( p r e [ r ] − p r e [ l − 1 ] ) ) ans=(2^{pre[r]-pre[l-1]}-1)*(2^{len-(pre[r]-pre[l-1])}) ans=(2pre[r]pre[l1]1)(2len(pre[r]pre[l1]))

题解:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char ch[1000000+10];
int num[1000000+10];
int pre[1000000+10];
const ll mod= 1e9+7;
int qpow(int a,int b)
{
    ll ans=1;
    ll ta=a,tb=b;
    while(tb){
        if(tb&1) ans=ans*ta%mod;
        tb>>=1;
        ta=ta*ta%mod;
    }
    return (int)(ans%mod);
}
int solve(int l,int r)
{
    int len=r-l+1;
    int n=pre[r]-pre[l-1];
    ll ans=(mod+qpow(2,n)-1)%mod;
    ll tmp=(qpow(2,len-n)-1+mod)%mod*ans%mod;
    ans=(ans+tmp)%mod;
    return (int)ans;
}
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    scanf("%s",ch+1);
    for(int i=1;i<=n;i++){
        num[i]=ch[i]-'0';
        pre[i]=pre[i-1]+num[i];
    }
    int l,r;
    for(int i=0;i<q;i++){
        scanf("%d%d",&l,&r);
        printf("%d\n",solve(l,r));
    }
    return 0;
}

D. Fun with Integers


题意: 给你一个正整数 n n n ( 2 &lt; = n ) (2&lt;=n) (2<=n)。一开始你可以选择任何一个整数开始变,a可以变成b的前提是:

  1. a是b的因数或者倍数
  2. a ! = b a!=b a!=b
  3. 之前a没有变成过b,b也没有变成过a。
  4. 2 &lt; = a , b &lt; = n 2&lt;=a,b&lt;=n 2<=a,b<=n

如果a是b的因数,从a变成b的话,你会获得 ∣ b a ∣ \left|\frac{b}{a}\right| ab的分数

如果a是b的倍数,从a变成b的话,你会获得 ∣ a b ∣ \left|\frac{a}{b}\right| ba的分数

问你能获得的最大分数是多少。

题解: 关键在于观察。
2 = &gt; 4 = &gt; − 2 = &gt; − 4 = &gt; 2 2 =&gt; 4 =&gt; -2 =&gt; -4 =&gt; 2 2=>4=>2=>4=>2 ,这是一个贡献为4*2的普通环,此时我们又回到了2,那么就可以接着转化:

2 = &gt; 6 = &gt; − 2 = &gt; − 6 = &gt; 2 2 =&gt; 6 =&gt; -2 =&gt; -6 =&gt;2 2=>6=>2=>6=>2
2 = &gt; 8 = &gt; − 2 = &gt; − 8 = &gt; 2 2 =&gt; 8 =&gt; -2 =&gt; -8 =&gt;2 2=>8=>2=>8=>2
2 = &gt; 10 = &gt; − 2 = &gt; − 10 = &gt; 2 2 =&gt; 10 =&gt; -2 =&gt; -10 =&gt;2 2=>10=>2=>10=>2
……
当2能转化到3的倍数的时候,这个时候我们就可以从3出发进行这样的循环(因为从2到6的时候,我们可以先把6变到3,等下再变回6,接着进行2的循环):
3 = &gt; 6 = &gt; − 3 = &gt; − 6 = &gt; 3 3 =&gt; 6 =&gt; -3 =&gt; -6 =&gt;3 3=>6=>3=>6=>3
3 = &gt; 9 = &gt; − 3 = &gt; − 9 = &gt; 3 3 =&gt; 9 =&gt; -3 =&gt; -9 =&gt;3 3=>9=>3=>9=>3
……

综上,我们可以发现,任意数字都可以当作开头,向着绝对值比它的数字变化。
所以对于2来说,2能带来的分数贡献就等于 ∑ i = 2 n / 2 4 ∗ i \sum_{i=2}^{n/2}4*i i=2n/24i,其他数字带来的贡献也是类似的,所以:
a n s = ∑ j = 2 n ∑ i = j n / j 4 ∗ i ans=\sum_{j=2}^{n}\sum_{i=j}^{n/j}4*i ans=j=2ni=jn/j4i
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    ll n,ans=0;
    scanf("%lld",&n);
    for(int i=2;i<=n;i++){
        ll x=n/i-1;
        ll tot=(2+1+x)*x;
        ans+=tot*2;
    }
    printf("%lld\n",ans);
    return 0;
}

E. Company


题意: :给你一颗由n个点组成的树,编号为1-n中的一个数字,并且各不相同。一共有q个询问,每次询问给你一个区间[L,R],让你求出在可以忽略区间中一个点的情况下,他们深度最大的lca是谁,并且输出忽略的是谁,lca是谁。

题解: 结论:对这棵树进行dfs序,然后一个区间的LCA一定是dfs序最小的点和dfs序最大的点的LCA。
证明挺简单的 这里忽略。
那么知道了这个结论之后我们只要先用线段树维护好区间最小的和最大的dfs序,每次询问到一个区间时,把dfs序最大的点拿掉,然后找第二大的点,求它和dfs序最小的点的lca,然后再把第一大的点放回来,再把第一小的点拿掉,再找到第二小的点和第一大的点求LCA,把两个答案进行比较后输出答案即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 100000+30;
int cnt=1,head[maxn],E[maxn*2];
struct Node
{
    int u,v,w,nxt;
}node[maxn*2];

void add(int u,int v,int w)
{
    node[cnt].u=u;
    node[cnt].v=v;
    node[cnt].w=w;
    node[cnt].nxt=head[u];
    head[u]=cnt++;
}
int dfn[maxn],dep[maxn],st[maxn*2][25];
void dfs(int rt,int fa,int d)
{
    dfn[rt]=++cnt;
    E[cnt]=rt;
    dep[rt]=d;
    for(int i=head[rt];i;i=node[i].nxt){
        dfs(node[i].v,rt,d+1);
        E[++cnt]=rt;
    }
}
void buildST()
{
    for(int i=1;i<=cnt;i++){
        st[i][0]=E[i];
    }
    for(int i=1;i<=24;i++){
        for(int j=1;j<=cnt;j++){
            if(j+(1<<i)-1>cnt) break;
            if(dep[st[j][i-1]]<dep[st[j+(1<<(i-1))][i-1]]) st[j][i]=st[j][i-1];
            else st[j][i]=st[j+(1<<(i-1))][i-1];
        }
    }
}
int lca(int a,int b)
{
    int x=a,y=b;
    if(x>y) swap(x,y);
    int j=1;
    int cnt=0;
    while(j*2<=y-x+1){
        j<<=1;
        cnt++;
    }
    if(dep[st[x][cnt]]<dep[st[y-j+1][cnt]]) return st[x][cnt];
    else return st[y-j+1][cnt];
}
int maxP[maxn*8],minP[maxn*8];
void build(int rt,int L,int R)
{
    if(L==R){
        maxP[rt]=dfn[L];
        minP[rt]=dfn[L];
    }
    else{
        int mid=L+R>>1;
        build(rt<<1,L,mid);
        build(rt<<1|1,mid+1,R);
        maxP[rt]=max(maxP[rt<<1],maxP[rt<<1|1]);
        minP[rt]=min(minP[rt<<1],minP[rt<<1|1]);
    }
}
int queryMax(int rt,int L,int R,int l,int r)
{
    if(L>=l&&R<=r) return maxP[rt];
    int mid=L+R>>1;
    if(mid>=r){
        return queryMax(rt<<1,L,mid,l,r);
    }
    else if(mid<l){
        return queryMax(rt<<1|1,mid+1,R,l,r);
    }
    else{
        return max(queryMax(rt<<1,L,mid,l,mid),queryMax(rt<<1|1,mid+1,R,mid+1,r));
    }
}
int queryMin(int rt,int L,int R,int l,int r)
{
    if(L>=l&&R<=r) return minP[rt];
    int mid=L+R>>1;
    if(mid>=r){
        return queryMin(rt<<1,L,mid,l,r);
    }
    else if(mid<l){
        return queryMin(rt<<1|1,mid+1,R,l,r);
    }
    else{
        return min(queryMin(rt<<1,L,mid,l,mid),queryMin(rt<<1|1,mid+1,R,mid+1,r));
    }
}
void update(int rt,int L,int R,int v,int x)
{
    if(L==R){
        maxP[rt]=minP[rt]=v;
    }
    else{
        int mid=L+R>>1;
        if(x<=mid){
            update(rt<<1,L,mid,v,x);
        }
        else{
            update(rt<<1|1,mid+1,R,v,x);
        }
        maxP[rt]=max(maxP[rt<<1],maxP[rt<<1|1]);
        minP[rt]=min(minP[rt<<1],minP[rt<<1|1]);
    }
}
int main()
{
    //printf("%d\n",1<<25);
    int n,q,x;
    scanf("%d%d",&n,&q);
    for(int i=2;i<=n;i++){
        scanf("%d",&x);
        add(x,i,1);
    }
    cnt=0;
    dfs(1,0,0);
    buildST();
    build(1,1,cnt);
    int l,r;
    for(int i=0;i<q;i++){
        scanf("%d%d",&l,&r);
        int a=queryMax(1,1,cnt,l,r);
        int b=queryMin(1,1,cnt,l,r);
        int p1=E[a],p2=E[b];
        //printf("#%d:\n%d %d %d %d\n",i,a,b,E[a],E[b]);
        update(1,1,cnt,0,p1);
        int t=queryMax(1,1,cnt,l,r);
        int ans1=lca(t,b);
        //printf("%d %d\n",t,ans1,dep[ans1]);
        update(1,1,cnt,a,p1);
        update(1,1,cnt,maxn*100,p2);
        t=queryMin(1,1,cnt,l,r);
        int ans2=lca(t,a);
        //printf("##%d\n",ans2);
        //printf("%d %d %d\n",t,ans2,dep[ans2]);
        update(1,1,cnt,b,p2);
        if(dep[ans1]>dep[ans2]){
            printf("%d %d\n",p1,dep[ans1]);
        }
        else{
            printf("%d %d\n",p2,dep[ans2]);
        }
    }
    return 0;
}

F. Upgrading Cities


题意: 给你一个有向无环连通图,n个点,m条边。定义一对点 ( u , v ) (u,v) (u,v)是连通的,当且仅当 从u出发可以达到v或者从v出发可以到达u。
定义一个点u为完全连通,当且仅当其他所有点都和它连通。
定义一个点v为半完全连通,当且仅当它不是完全连通并且存在某一个点,去掉它后v是完全连通。

问你完全连通和半完全连通的点加起来一共有多少个。

题解:

解法1:

  1. 当一个点u出度为0的时候,如果点u是完全连通的,当且仅当没有其他出度为0的点。
  2. 当一个点u出度为0的时候并且有其他一个点v出度为0的时候,如果点v是半完全连通的,当且仅当指向点v的点中不存在出度为1的点,即每一个指向点v的点中没有一个是单纯只指向点v的。

通过这两条规则,我们对于一开始出度为0的点很容易就能进行判断,那么判断完一个点后,就可以把该点删去,然后把指向这个点的所有父亲点的出度-1,像拓扑排序一样,不停维护一个出度为0的点的集合,直到没有剩下的点。

然后把整个图的边反向一下,再做一遍就可以找出所有的点。
ac代码就是通过解法1 做的,但是有一些trick。
cnt代表了出度为0的点的数量,x表示了这些点异或起来的值,当cnt==1的时候,x刚好是另外那个出度为0的点的下标。

剩下的言语有点解释不清,只能从代码里感悟一下了。

解法2:(官方题解里的做法)
首先,考虑完全连通点。
不难发现,所有的完全连通点,一定在这个有向无环图的任意一条直径上,所以选定任意一条直径,对于直径上的每一个点,往下搜索,可以得出这个从点出发能够到达的点的数量,记为 o u t i out_i outi,由于每个点只需要被访问一次,所以到目前为止求直径和算直径上每个点能到达的点的数量,复杂度都是 O ( N + M ) O(N+M) O(N+M)的。
反着建图再搜一次,然后通过能到达的点的数量,记为 i n i in_i ini。遍历一遍直径上的所有点,如果 i n i + o u t i = = n − 1 in_i+out_i==n-1 ini+outi==n1,那么点i就是完全连通点。这样就可以求出所有完全连通点。

接着我们考虑半完全连通点。
如果半完全连通点,在那条直径上,只需要判断 i n i + o u t i in_i+out_i ini+outi是不是等于 n − 2 n-2 n2即可。

对于那些不在直径上的点,还没搞懂。。
等待填坑。。。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int> G[300005],rG[300005],path;
int in[300005],out[300005],num[300005],tot[300005],bel[300005];
void solve(vector<int> H[])
{
    int cnt=0,x=0;
    memset(out,0,sizeof(out));
    memset(tot,0,sizeof(tot));
    for(int i=0;i<path.size();i++){
        int t=path[i];
        for(int j=0;j<H[t].size();j++){
            int tt=H[t][j];
            if(out[tt]==0){
                cnt--;
                x^=tt;
                bel[tt]=t;
                tot[t]++;
            }
            else if(out[tt]==1){
                tot[bel[tt]]--;
                bel[tt]=-1;
            }
            out[tt]++;
        }
        if(cnt==1&&tot[x]==0) num[t]++;
        else if(cnt!=0) num[t]+=2;
        cnt++;
        x^=t;
    }
}
int main()
{
    int n,m,u,v;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        in[v]++;
        rG[v].push_back(u);
    }
    queue<int> q;
    for(int i=1;i<=n;i++){
        if(in[i]==0)
            q.push(i);
    }
    while(!q.empty()){
        int x=q.front();
        path.push_back(x);
        q.pop();

        for(int i=0;i<G[x].size();i++){
            in[G[x][i]]--;
            if(in[G[x][i]]==0) q.push(G[x][i]);
        }
    }
    solve(rG);
    reverse(path.begin(),path.end());
    solve(G);

    int ans=0;
    for(int i=1;i<=n;i++){
        if(num[i]<=1) ans++;
    }
    printf("%d\n",ans);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值