Codeforces Round #396 (Div. 2)

A. Mahmoud and Longest Uncommon Subsequence

题意:
求最长不公共序列

解法:
如果两个字符串完全一样,那么必定不满足题意,否则长的那一段必定不为另一段的子序列,所以取两段最值即可。

#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
const int N=100005;
char s1[N],s2[N];


int main()
{
    scanf("%s %s",s1,s2);
    int l1=strlen(s1),l2=strlen(s2);
    if(l1==l2){
        if(strcmp(s1,s2)==0){
            printf("-1\n");
        }else{
            printf("%d\n",l1);
        }
    }else{
        printf("%d\n",max(l1,l2));
    }

    return 0;
}

B. Mahmoud and a Triangle

题意:
给长度为n(n<=10^5)的数组,问能否找出三个数字使其可以成为三角形的边。

解法:
直接对数组进行排序,对相邻的三个数,i,i-1,i-2进行判断,如果满足a[i] < a[i-1]+a[i-2],那么此时已经找到满足三角形的三条边。如果不满足,那么对于a[i-2]可以不用进行判断了,因为判断是否有其他三角形的时候可以用更大的a[i-1]来作为最小边,这样更有可以凑成三角形。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100005;
int a[N];
int main()
{
    int n;
    scanf("%d",&n);

    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    sort(a+1,a+n+1);
    int flag=0;
    for(int i=3;i<=n;i++){
        if(a[i]<a[i-1]+a[i-2])flag=1;
    }

    printf("%s\n",flag==1?"YES":"NO");

    return 0;
}

C. Mahmoud and a Message

题意:给一个长度为n(n<=1000)的字符串,字符串只包含26小写字母,对字符串进行分割,求分割字符串的方案数。其中,每个小写字母在分割后的每一段中存在一个限制值ai,即分割后的那一段的长度不能超过ai。接着求出所有方案中子串最长的长度和最小的分割段数。

解法:
这题一共有三个输出,其实是三个问题,可以单独解决。
求分割的方案数,那么我们需要维护一个dp值,dp[i]表示第1~i的方案数,dp[i]的更新可以从1~i-1转移,枚举j从i-1~1,如果j~i满足,那么我们就把dp[j-1]的方案数加入到dp[i]中即可。同时可以维护子串的最长长度。
求最小的分割段数,也是同理,维护ans3值,ans3[i]表示1~i的最小分割数,转移方式同上。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define mod 1000000007
const int N=1005;
int dp[N];
int ans3[N];
char str[N];
int a[30];
int main()
{
    int n;
    scanf("%d",&n);
    scanf("%s",str+1);
    for(int i=0;i<26;i++) scanf("%d",&a[i]);
    dp[0]=1;
    int ans2=1;
    ans3[0]=0;
    for(int i=1;i<=n;i++)
    {
        dp[i]=dp[i-1];
        ans3[i]=ans3[i-1]+1;
        int mlen=a[str[i]-'a'];
        for(int j=i-1;j>=1;j--)
        {
            int len=i-j+1;
            mlen=min(mlen,a[str[j]-'a']);
            if(mlen<len) break;
            ans2=max(ans2,len);
            dp[i]=(dp[i]+dp[j-1])%mod;
            ans3[i]=min(ans3[i],ans3[j-1]+1);
        }
    }
    printf ("%d\n",dp[n]);
    printf("%d\n",ans2);
    printf("%d\n",ans3[n]);
    return 0;
}

D. Mahmoud and a Dictionary

题意:
给n(n<100000)个单词,单词有相似意思和相反意思的关系。有m对关系,但可能存在错误的关系,如果是正确的关系输出YES,错误关系输出NO。q个询问,询问x,y直接的关系,相似输出1,相反输出2。

解法:
其实这题和朋友的朋友是朋友,朋友的敌人是敌人是类似的。用并查集可以解决。
对于每个词创建2个元素,i-A和i-B(i-x表示i属于种类x),并且用2*N个元素建立并查集,并查集的每一个组表示组内的所有元素代表的情况同时发生或者不发生。
x和y相似,合并x-A和y-A,x-B和y-B
x和y不相似,合并x-A和y-B,x-B和y-A
合并之前要检查是否矛盾

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<String>
using namespace std;
const int N=100005;

map<string,int>mp;

int fa[N*2];

int Find(int x)
{
    if(fa[x]!=x)return fa[x]=Find(fa[x]);
    return fa[x];
}

void Merge(int x,int y)
{
    int fx=Find(x),fy=Find(y);
    if(fx!=fy)fa[fy]=fx;
    return ;
}

bool same(int x,int y)
{
    if(Find(x)==Find(y))return 1;
    return 0;
}

int main()
{
    int n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    char str[25];
    for (int i=1;i<=n;i++){
        scanf(" %s",str);
        mp[str]=i;
    }
    int t;char s1[25],s2[25];
    for(int i=1;i<=2*n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%d %s %s",&t,s1,s2);
        int x=mp[s1],y=mp[s2];
        if(t==1){
            if(same(x,y+n)){
                puts("NO");
            }else{
                Merge(x,y);
                Merge(x+n,y+n);
                puts("YES");
            }
        }else{
            if(same(x,y)){
                puts("NO");
            }else{
                Merge(x,y+n);
                Merge(x+n,y);
                puts("YES");
            }
        }
    }

    for(int i=1;i<=q;i++){
        scanf("%s %s",s1,s2);
        int x=mp[s1],y=mp[s2];
        if(same(x,y)){
            puts("1");
        }else if(same(x,y+n)){
            puts("2");
        }else puts("3");
    }

    return 0;
}

方法2:
带权并查集来解决。引入rank数组 rank[i]=0,表示i和i的父节点意思相同,rank[i]=1表示i和i的父节点意思不相同。
更新时需要用向量的思想。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<string>
using namespace std;

const int N=100005;

int fa[N];
int rank[N];
int n,m,q;
map<string,int>mp;

void init()
{
    for(int i=1;i<N;i++) fa[i]=i;
    memset(rank,0,sizeof(rank));
}


int Find(int x)
{
    if(fa[x]!=x){
        int oldfa=fa[x];//oldfa即x原先的父亲
        fa[x]=Find(fa[x]);
        rank[x]=(rank[x]+rank[oldfa])%2; //更新x和新的父亲的关系,x->newfa = x->oldfa+oldfa->newfa
    }
    return fa[x];
}



int main()
{
    init();
    char str[25];
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++){
        scanf("%s",str);
        mp[str]=i;
    }
    int t;char s1[25],s2[25];
    for(int i=1;i<=m;i++){
        scanf("%d %s %s",&t,s1,s2);t--;
        int o1=mp[s1],o2=mp[s2];
        int f1=Find(o1),f2=Find(o2);
        if(f1!=f2){
            fa[f2]=f1;
            rank[f2]=(t-rank[o2]+rank[o1])%2;//更新f2和f1关系, f2->f1 = o2->o1+o1->f1+f2->o2
            puts("YES");
        }else{
            int rel=(2-rank[o2]+rank[o1])%2;//确定o1,o2关系,o1->fa+f2->o2=o1->o2
            if(rel!=t)puts("NO");
            else puts("YES");
        }
    }

    for(int i=1;i<=q;i++){
        scanf(" %s %s",s1,s2);
        int o1=mp[s1],o2=mp[s2];
        int f1=Find(o1),f2=Find(o2);
        if(f1!=f2)puts("3");
        else{
            int rel=(2-rank[o2]+rank[o1])%2;
            printf("%d\n",1+rel);
        }
    }

    return 0;
}

E. Mahmoud and a xor trip

题意:给一个节点数为n的树,每个节点有个权值,两个节点的路径的值定义为路径上所有节点权值的异或值。求所有节点对的值的和。
解法:树型dp

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define ll __int64
using namespace std;
const int N=100005;

int head[N],top=0;
ll ans=0;
int a[N],dp[N][25][2];
struct Edge
{
    int v,next;
}edge[N*2];

void addedge(int u,int v)
{
    edge[top].v=v;
    edge[top].next=head[u];
    head[u]=top++;
}

void init()
{
    ans=0;
    top=0;
    memset(head,-1,sizeof(head));
}

int sum[N][2];
ll cnt=0;
int b[N];
void dfs(int u,int fa)
{
    sum[u][0]=sum[u][1]=0;
    sum[u][b[u]]++;
    cnt+=b[u];
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v==fa)continue;
        dfs(v,u);
        cnt+=sum[u][1]*sum[v][0]+sum[u][0]*sum[v][1];
        sum[u][b[u]]+=sum[v][0];
        sum[u][b[u]^1]+=sum[v][1];
    }
}

int main()
{
    init();
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int u,v;
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);
    }

    for(int i=0;i<=20;i++){
        for(int j=1;j<=n;j++) {
                b[j]=(a[j]>>i)&1;
        }
        cnt=0;
        dfs(1,-1);
        ans+=cnt*(ll)(1<<i);
    }
    cout<<ans<<endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值