Codeforces Round #453 (Div. 1)解题报告(ABCD)

本文解析了四道经典算法题目,包括树的哈希、多项式的最大公约数构造、二部图子段查询及树的权重分配问题。通过巧妙构造与图论技巧,给出简洁有效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A.Hashing Trees

题目大意:给定数组a[i],其中a[i]表示深度为i的节点有a[i]个,问是否存在两个不同构的树同时满足这个条件。如果有,请输出。
简要题解:有很多种构造方案。我的做法是,第一次把所有深度为i的节点都向深度为i-1的第一个节点连边。第二次,尝试分出一个深度为i的节点向深度为i-1的第二个节点连边。如果可以这样生成两棵树,则有解。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#define maxn 200010

using namespace std;

vector<int> v[maxn];
int a[maxn],f[maxn];
int n,h,cnt;
bool w=1;

int main()
{
    scanf("%d",&h);
    for (int i=0;i<=h;i++) scanf("%d",&a[i]);
    cnt=1;v[0].push_back(1);
    for (int i=1;i<=h;i++)
    {
        if (w && a[i]!=1 && v[i-1].size()>1)
        {
            w=0;v[i].push_back(++cnt);f[cnt]=v[i-1][1];
            for (int j=1;j<a[i];j++) v[i].push_back(++cnt),f[cnt]=v[i-1][0];
        }
        else for (int j=1;j<=a[i];j++) v[i].push_back(++cnt),f[cnt]=v[i-1][0];
    }
    if (w) puts("perfect");
    else
    {
        puts("ambiguous");
        printf("0");cnt=1;
        for (int i=1;i<=h;i++)
        {
            for (int j=1;j<=a[i];j++) printf(" %d",cnt);
            cnt+=a[i];
        }
        printf("\n");
        printf("0");
        for (int i=1;i<=h;i++)
        {
            for (int j=0;j<v[i].size();j++) printf(" %d",f[v[i][j]]);
        }
        printf("\n");
    }
    return 0;
}

B. GCD of Polynomials

题目大意:构造两个度数不超过n的多项式,使他们求GCD时,调用GCD过程的次数恰好为n。要求每一项的系数为-1或0或1。
简要题解:注意到,每调用一次GCD过程,多项式的次数都会减一。所以最后的答案多项式一定是n次和n-1次的。考虑用最初的x和1,递推到n次。我使用的做法是,每次枚举将高次多项式乘(x+(-1/0/1)),低次多项式乘-1/1,构造出新的高次多项式。最后构造出解。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<bitset>

using namespace std;

int da[6]={-1,-1,0,0,1,1};
int db[6]={1,-1,1,-1,1,-1};
int n,m; 
int a[210][210],b[210][210];
int c[210];

bool solve(int x)
{
    if (x==n) return 1;
    x++;    
    for (int k=0;k<6;k++)
    {
        a[x][0]=da[k]*a[x-1][0]+db[k]*b[x-1][0];
        for (int i=1;i<=x;i++) a[x][i]=a[x-1][i-1]+da[k]*a[x-1][i]+db[k]*b[x-1][i];
        bool w=1;
        for (int i=0;i<=x;i++) if (a[x][i]>=2 || a[x][i]<=-2) {w=0;break;}
        if (w) 
        {
            for (int i=0;i<x;i++) b[x][i]=a[x-1][i];
            if (solve(x)) return 1;
        }
    }
    return 0;
}

int main()
{
    scanf("%d",&n);
    a[1][1]=1;b[1][0]=1;
    if (solve(1))
    {
        printf("%d\n",n);
        for (int i=0;i<=n;i++) printf("%d ",a[n][i]);printf("\n");
        printf("%d\n",n-1);
        for (int i=0;i<n;i++) printf("%d ",b[n][i]);printf("\n");
    }
    else puts("-1");
    return 0;
}

C.Bipartite Segments

题目大意:给定一个无偶环的无向图,m次询问,每次询问[l,r]中能取出多少个子区间[x,y],满足只选择[x,y]中的点和边,不会形成奇环。
简要题解:以每个位置i,都有一个最靠左的端点pre[i],满足[pre[i],i]的所有后缀都满足不形成奇环。因为原图没有偶环,所以只要形成环就不满足条件,问题简单了许多。pre[i]是单调递增的,故可以用整体二分来求。对于每组询问,只需要维护一个前缀和就可以解决。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#define maxn 300010

using namespace std;

vector<int> v[maxn];
int f[maxn],pre[maxn],st[maxn];
bool vis[maxn];
long long sum1[maxn],sum2[maxn];
int n,m,T,top;

int find(int i) 
{
    if (f[i]==i) return i;
    if (!vis[i]) st[++top]=i,vis[i]=1;
    return f[i]=find(f[i]);
}

void solve(int l,int r,int L,int R)
{
    if (l>r) return;
    if (L==R)
    {
        for (int i=l;i<=r;i++) pre[i]=L;
        return;
    }
    int mid=(L+R)>>1;
    int ans=r+1;
    bool w=1;
    for (int i=mid;i<=r;i++)
    {
        int j=lower_bound(v[i].begin(),v[i].end(),mid)-v[i].begin();
        for (;j<v[i].size();j++)
        {
            int f1=find(v[i][j]),f2=find(i);
            if (f1!=f2) 
            {
                f[f1]=f2;
                if (!vis[f1]) st[++top]=f1,vis[f1]=1;
            }
            else {w=0;break;}
        }
        if (!w) {ans=i;break;}
    }
    while (top) f[st[top]]=st[top],vis[st[top]]=0,top--;
    solve(l,ans-1,L,mid);solve(ans,r,mid+1,R);
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        v[max(x,y)].push_back(min(x,y));
    }
    for (int i=1;i<=n;i++) sort(v[i].begin(),v[i].end());
    for (int i=1;i<=n;i++) f[i]=i;
    solve(1,n,1,n);
    //for (int i=1;i<=n;i++) printf("%d ",pre[i]);printf("\n");
    for (int i=1;i<=n;i++) sum1[i]=sum1[i-1]+i,sum2[i]=sum2[i-1]+(i-pre[i]+1);
    scanf("%d",&T);
    while (T--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        long long ans=0;
        int now=upper_bound(pre+l,pre+r+1,l)-pre;
        //printf("%d\n",now);
        ans=sum1[now-1]-sum1[l-1]-1ll*(now-l)*(l-1)+sum2[r]-sum2[now-1];
        printf("%I64d\n",ans);
    }
    return 0;
}

D.Weighting a Tree

题目大意:一个无向连通图,每个点有-n到n的权值,构造一种方案,给每条边赋值,使每个点相邻的边的权值和等于这个点的权值。
简要题解:如果这个图是个二分图,那么无论如何赋值,左右两边的和必然相等,则取原图的一棵生成树,只使用树边来进行赋值即可。如果原图不是二分图,我们可以找到一条连接同一部分的边,那么把这条边的权值赋为两部分差的一半即可。按照二分图进行分类的方法,也是很厉害了。

<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 100010

using namespace std;

int head[maxn],nxt[maxn*2],to[maxn*2],fa[maxn],fr[maxn];
int dep[maxn];
long long ans[maxn],w[maxn];
int n,m,num,e;
long long sum[2];

void addedge(int x,int y) 
{
    num++;to[num]=y;nxt[num]=head[x];head[x]=num;
}

void dfs(int x)
{
    sum[dep[x]&1]+=w[x];
    for (int p=head[x];p;p=nxt[p])
      if (to[p]!=fa[x])
      {
        if (!dep[to[p]]) dep[to[p]]=dep[x]+1,fa[to[p]]=x,fr[to[p]]=p,dfs(to[p]);
        else if ((dep[x]-dep[to[p]]+1)&1) e=p;
      }
}

void solve(int x)
{
    for (int p=head[x];p;p=nxt[p])
      if (fa[to[p]]==x) solve(to[p]),w[x]-=ans[p/2];
    ans[fr[x]/2]=w[x];w[x]-=ans[fr[x]/2];
}

int main()
{
    num=1;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%I64d",&w[i]);
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        addedge(x,y);addedge(y,x);
    }
    dep[1]=1;dfs(1);
    if ((sum[1]-sum[0])&1) {puts("NO");return 0;}
    if (!e && sum[1]!=sum[0]) {puts("NO");return 0;}
    if (e && sum[1]!=sum[0])
    {
        if (dep[to[e]]&1) ans[e/2]=(sum[1]-sum[0])/2,sum[1]=sum[0];
        else ans[e/2]=(sum[0]-sum[1])/2,sum[0]=sum[1];
        w[to[e]]-=ans[e/2];
        w[to[e^1]]-=ans[e/2];
    }
    solve(1);
    puts("YES");
    for (int i=1;i<=m;i++) printf("%I64d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值