Gourmet choice CodeForces - 1131D (并查集/tarjan缩点+拓扑排序上dp)

传送门

题意:有个人吃了两天饭,第一天吃了n个菜,第二天吃了m个菜,然后给出一个矩阵,矩阵上第i行第j列对应着第1天的第i道菜和第二天的第j道菜的关系。

题解:这题,一眼看上去真的是和差分约束没啥区别,打了一发,T10,优化到了T13,实在无能为力,有大佬优化好的可以贴下评论。

附上代码:

#include<bits/stdc++.h>
#define IO ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
using namespace std;
const int maxn=1e3+5;
const int maxm=1e3+5;
const int maxe=2*maxn*maxm;
char s[5];
struct edge{int v,w,next;}edges[maxe];;
int head[maxn+maxm],tot;
void init()
{
    tot=1;
}
void add_edges(int u,int v,int w)
{
    edges[tot].v=v;edges[tot].w=w;edges[tot].next=head[u];head[u]=tot++;
}
int n,m,dis[maxn+maxm],cnt[maxn+maxm];
bool vis[maxn+maxm];
int sta[10*(maxn+maxm)];
bool spfa()
{
    int s=0,t=0;
    sta[t++]=n+m+1;
    dis[n+m+1]=1;cnt[n+m+1]=1;vis[n+m+1]=true;
    while(s<t){
        int u=sta[--t];
        vis[u]=false;
        for(int i=head[u];i;i=edges[i].next){
            edge e=edges[i];
            if(dis[e.v]<dis[u]+e.w){
                dis[e.v]=dis[u]+e.w;
                if(!vis[e.v]){
                    sta[t++]=e.v;
                    vis[e.v]=true;
                    if(++cnt[e.v]>n+m+1){
                        printf("No\n");
                        exit(0);
                    }
                }
            }
        }
    }
    return true;
}
int main()
{
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%1s",s);
            if(s[0]=='>')add_edges(j+n,i,1);
            else if(s[0]=='<')add_edges(i,j+n,1);
            else{
                add_edges(j+n,i,0);add_edges(i,j+n,0);
            }
        }
    }
    for(int i=1;i<=n+m;i++)add_edges(n+m+1,i,0);
    if(spfa()){
        printf("Yes\n");
        for(int i=1;i<=n;i++)printf("%d ",dis[i]);
        printf("\n");
        for(int i=1;i<=m;i++)printf("%d ",dis[i+n]);
        printf("\n");
    }else{
        printf("No\n");
    }
    return 0;
}

这样不行,但是可以发现这个题是这个传送门的弱化版,可以将小于号从小到大连一条边,大于号也是转化为小于号,从小到达连一下,还可以将等于号转化成双向边,然后进行tarjan寻找环缩点,之后再特判一遍是否环上所有的点都相等,然后之后缩点后重新建图,这一个是一个标准的拓扑排序,直接在这个拓扑排序上进行dp即可。

附上代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+50;
char s[1050][1050];
int n,m;
struct edge{int v,next;}e1[maxn*maxn*2],e2[maxn*maxn*2];
int head1[maxn*2],head2[maxn*2],tot1,tot2;
int cnt,top,dfn[maxn*2],low[maxn*2],ltk[maxn*2],sta[maxn*2];
int du[maxn*2],h,t,que[maxn*2],dis[maxn*2];
void add1(int u,int v)
{
    e1[++tot1].v=v;e1[tot1].next=head1[u];head1[u]=tot1;
}
void add2(int u,int v)
{
    du[v]++;
    e2[++tot2].v=v;e2[tot2].next=head2[u];head2[u]=tot2;
}
void dfs(int x)
{
    dfn[x]=low[x]=++cnt;sta[++top]=x;
    for(int i=head1[x];i;i=e1[i].next){
        if(!dfn[e1[i].v]){
            dfs(e1[i].v);
            low[x]=min(low[x],low[e1[i].v]);
        }else if(!ltk[e1[i].v]){
            low[x]=min(low[x],dfn[e1[i].v]);
        }
    }
    if(dfn[x]==low[x]){
        ltk[0]++;
        while(top){
            ltk[sta[top]]=ltk[0];
            if(sta[top--]==x)break;
        }
    }
}
void rebuild()
{
    for(int i=1;i<=n+m;i++){
        for(int j=head1[i];j;j=e1[j].next){
            if(ltk[i]!=ltk[e1[j].v]){
                add2(ltk[i],ltk[e1[j].v]);
            }
        }
    }
}
void toposort()
{
    for(int i=1;i<=ltk[0];i++){
        if(!du[i]){
            que[++t]=i;
            dis[i]=1;
        }
    }
    while(h<t){
        int x=que[++h];
        for(int i=head2[x];i;i=e2[i].next){
            dis[e2[i].v]=dis[x]+1;
            if(!--du[e2[i].v]){
                que[++t]=e2[i].v;
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>s[i][j];
            if(s[i][j]=='='){
                add1(i,j+n);add1(j+n,i);
            }else if(s[i][j]=='>'){
                add1(j+n,i);
            }else{
                add1(i,j+n);
            }
        }
    }
    for(int i=1;i<=n+m;i++){
        if(!dfn[i]){
            dfs(i);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(s[i][j]!='='){
                if(ltk[i]==ltk[j+n]){
                    cout<<"No"<<endl;
                    return 0;
                }
            }
        }
    }
    rebuild();toposort();
    cout<<"Yes"<<endl;
    for(int i=1;i<=n;i++)cout<<dis[ltk[i]]<<" ";cout<<endl;
    for(int i=1;i<=m;i++)cout<<dis[ltk[i+n]]<<" ";cout<<endl;
    return 0;
}

之后再网上还看到一种就是,可以将相等的点处理到一个并查集中,也就是说先处理第一遍,把认为相等的点放到一个并查集中,然后进行特判下,之后将按照小于号,将一个个并查集连接起来,还得处理一番,讲多重边去掉,然后如果是答案的话一定是一个有向无环图,然后在上面进行拓扑排序和dp,并带着处理一番环即可。

附上代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5;
vector<int>e[maxn*2];
char s[maxn][maxn];
int n,m;
int d[maxn*2],in[maxn*2],f[maxn*2];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
void merge(int a,int b)
{
    int x=find(a),y=find(b);
    if(x!=y)f[y]=x;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n+m;i++)f[i]=i;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>s[i][j];
            if(s[i][j]=='=')merge(find(i),find(j+n));
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(s[i][j]!='='){
                if(find(i)==find(j+n)){
                    cout<<"No"<<endl;
                    return 0;
                }
                if(s[i][j]=='>'){
                    e[find(j+n)].push_back(find(i));
                }else if(s[i][j]=='<'){
                    e[find(i)].push_back(find(j+n));
                }
            }
        }
    }
    for(int i=1;i<=n+m;i++)sort(e[i].begin(),e[i].end());
    for(int i=1;i<=n+m;i++)e[i].erase(unique(e[i].begin(),e[i].end()),e[i].end());
    for(int i=1;i<=n+m;i++){
        for(int j=0;j<e[i].size();j++)in[e[i][j]]++;
    }
    queue<int>q;
    for(int i=1;i<=n+m;i++){
        if(!in[i]){
            q.push(i),d[i]=1;
        }
    }
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=0;i<e[u].size();i++){
            int nex=e[u][i];
            in[nex]--;
            d[nex]=d[u]+1;
            if(in[nex]==0)q.push(nex);
        }
    }
    if(*max_element(in+1,in+n+m+1)>0){
        cout<<"No"<<endl;
        return 0;
    }
    cout<<"Yes"<<endl;
    for(int i=1;i<=n;i++)cout<<d[find(i)]<<" ";cout<<endl;
    for(int i=1;i<=m;i++)cout<<d[find(i+n)]<<" ";cout<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhouzi2018/article/details/87921074
今日推荐