11.24ACM补题

CodeForces 989C

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int a,b,c,d;
char mapp[51][51];
int main(){
    scanf("%d%d%d%d",&a,&b,&c,&d);
    a--,b--;
    for(int i=1;i<=25;i++)
    for(int j=1;j<=50;j++)
    mapp[i][j]='a';
    for(int i=26;i<=50;i++)
    for(int j=1;j<=50;j++)
    mapp[i][j]='b';
    for(int i=1;i<=25;i+=2)
    for(int j=1;j<=50;j+=2)
    if(b)mapp[i][j]='b',b--;
    else if(c)mapp[i][j]='c',c--;
    else if(d)mapp[i][j]='d',d--;
    for(int i=27;i<=50;i+=2)
    for(int j=1;j<=50;j+=2)
    if(a)mapp[i][j]='a',a--;
    else if(c)mapp[i][j]='c',c--;
    else if(d)mapp[i][j]='d',d--;
    puts("50 50");
    for(int i=1;i<=50;i++){
        for(int j=1;j<=50;j++)
        cout<<mapp[i][j];
        cout<<endl;
    }
    return 0;
}
View Code

这就是其实很简单但就是想不出来的题目

如果考虑n和m的大小的话,我没有思路,一开始也因为这个放弃了

但其实题目没有要求n和m的大小,所以全部取最大50就行了

然后先分成上下ab两部分,然后在不改变原来连通块的基础上增加a,b,c,d的连通个数

需要注意一点就是——如果1到25行全部染成a,在26到50行增加a的连通时要从27开始。毕竟26染成a后可能和25行连通了

 CodeForces - 489D

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn=3007;
struct edge{
    int to,next;
}e[maxn*10];
int n,m,head[maxn],a,b,tot,d[maxn],c[maxn][maxn];
void add(int u,int v){
    e[tot].next=head[u];
    e[tot].to=v;
    head[u]=tot++;
}
void init(){
    c[0][0]=1;
    for(int i=1;i<maxn;i++){
        c[i][0]=1;
        for(int j=1;j<=i;j++)
        c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
}
int main(){
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    head[i]=-1;
    while(m--){
        scanf("%d%d",&a,&b);
        add(a,b);
    }
    int ans=0;
    for(int u=1;u<=n;u++){
        for(int i=head[u];i!=-1;i=e[i].next){
            int v=e[i].to;
            for(int j=head[v];j!=-1;j=e[j].next){
                int vv=e[j].to;
                if(vv==u)continue;
                d[vv]++;
            }
        }
        for(int i=1;i<=n;i++)
        if(d[i]){
            ans+=c[d[i]][2];
            d[i]=0;
        }
    }
    printf("%d
",ans);
    return 0;
}
View Code

比较简单的图论

起点u通过两个中转点v1,v2到达终点vv,问这种情况有多少种

其实就是遍历1到N作为起点,然后记录达到vv的个数,然后能找到多少种两条,答案就能增加多少

 CodeForces - 437C 

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn=1007;
struct edge{
    int to,next,fro;
}e[maxn*4];
int n,m,head[maxn],a,b,tot,v[maxn];
bool vis[maxn];
void add(int u,int v){
    e[tot].next=head[u];
    e[tot].to=v;
    e[tot].fro=u;
    head[u]=tot++;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%d",&v[i]);
    while(m--){
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    int ans=0;
    for(int i=0;i<tot;i++)
    ans+=min(v[e[i].to],v[e[i].fro]);
    printf("%d
",ans/2);
    return 0;
}
View Code

同样很简单的图论

分析一下就能看出——剪每条边都必须要增加边两端其中一个顶点的值

而考虑剪法,其实就是选择两个顶点中值较小的那个

所以遍历一下边,增加两个顶点中值最小的就行了

 CodeForces - 500B 

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn=307;
int f[maxn],n,p[maxn],pos[maxn],ans[maxn];
char ch;
int get(int x){
    if(f[x]==x)return x;
    else return f[x]=get(f[x]);
}
void join(int a,int b){
    int fa=get(a),fb=get(b);
    if(fa==fb)return;
    f[fa]=fb;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&p[i]);
        pos[p[i]]=i;
        f[i]=i;
    }
    for(int i=1;i<=n;i++){
        getchar();
        for(int j=1;j<=n;j++){
            ch=getchar();
            if(ch=='1')join(i,j);
        }
    }
    for(int i=1;i<=n;i++){
        int posi=pos[i];
        for(int j=1;j<=n;j++)
        if(!ans[j]&&get(j)==get(posi)){
            ans[j]=i;
            break;
        }
    }
    for(int i=1;i<=n;i++)
    cout<<ans[i]<<' ';
    cout<<endl;
    return 0;
}
View Code

这也不算图论的题吧,就是将能转移的位置并在一起

然后让小的数尽量移动到前面去

CodeForces - 1039C

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<set>
using namespace std;
typedef long long LL;
const int maxn=500007;
const int mod=1e9+7;
int n,m,k,sz,num,f[maxn];
LL val[maxn],ans,t;
set<int>st;
struct edge{
    int u,v;
    LL w;
    bool operator<(const edge &a)const{
        return w<a.w;
    }
}e[maxn];
int get(int x){
    if(f[x]==x)return x;
    else return f[x]=get(f[x]);
}
void join(int a,int b){
    int fa=get(a),fb=get(b);
    if(fa==fb)return;
    f[fa]=fb;
    st.insert(a);st.insert(b);
    sz--;
}
LL qow(LL a,int b){
    LL ans=1;
    while(b){
        if(b&1)
            ans=(ans*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans;
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
        scanf("%lld",&val[i]);
    for(int i=0;i<m;i++){
        scanf("%d%d",&e[i].u,&e[i].v);
        e[i].w=val[e[i].u]^val[e[i].v];
    }
    sort(e,e+m);
    t=-1;
    for(int i=1;i<=n;i++)
        f[i]=i;
    for(int i=0;i<m;)
    if(e[i].w!=t){
        t=e[i].w;
        num++;sz=n;
        while(e[i].w==t){
            join(e[i].u,e[i].v);
            i++;
        }
        ans+=qow(2,sz);
        ans%=mod;
        for(auto it:st)f[it]=it;
        st.clear();
    }
    ans+=((qow(2,k)+mod-num)%mod)*(qow(2,n)%mod);
    ans%=mod;
    printf("%lld
",ans);
    return 0;
}
View Code

这道题题目又长,异或又不熟,还有卡时间,是道很不友好的题

但是仔细分析后还是能做出来的

毕竟一个人总是喜欢把问题想复杂,这样他就可以放弃思考

而这道题,本质上也就是并查集加快速幂而已

如果你非要死磕建图,最后只会越想越复杂

所以做这道题学到的一点就是——当你怎么也想不出来的时候,最好换其他思路,不然会被饶进死圈子

然而即便我做过这道题,知道做法,还是TLE了几次

第一次TLE——恢复并起来的点的时候,不要直接1-n遍历恢复,而是将并过的点记录下来,然后恢复

第二次TLE——记录有多少个连通块的时候,从1-n找f[i]==i要找n次,而并过一次将sz--就比n次少很多,最后两个方法都能找到有多少连通块,不过第二种省时间能过

CodeForces - 875F

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=2e5+7;
int n,m,f[maxn],ans;
bool cir[maxn];
struct Node{
    int a,b,v;
    bool operator<(const Node &a)const{
        return v>a.v;
    }
}node[maxn];
int get(int x){
    if(f[x]==x)return x;
    else return f[x]=get(f[x]);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&node[i].a,&node[i].b,&node[i].v);
    sort(node+1,node+1+m);
    for(int i=1;i<=n;i++)
        f[i]=i;
    for(int i=1;i<=m;i++){
        int fa=get(node[i].a),fb=get(node[i].b);
        if(fa!=fb){
            if(cir[fa]&&cir[fb])continue;
            ans+=node[i].v;
            f[fa]=fb;
            cir[fb]=cir[fb]|cir[fa];
        }
        else if(!cir[fa]){
            ans+=node[i].v;
            cir[fa]=true;
        }
    }
    printf("%d
",ans);
    return 0;
}
View Code

啊这道题我也被骗了。

想着每个公主能选两个王子,不就是个容量为2的二分图匹配吗

如果是想找最多的找到王子的公主的个数的话,这个思路没错。但是公主有权值,要让权值最大,这个思路就不行了

然后再想会不会是什么 带权二分图匹配,不过网上查了没有这种做法

所以看了题解明白了这又是一道并查集的题


如果把公主i喜欢的王子ai,bi并在一起。那么一个非环连通块的点个数=边个数+1,表示n个公主在这n+1个王子中选到了n个王子。而如果是一个环,边数=点数,表示n个公主恰好分完了这n个王子。所以我们要做的,就是贪心+并查集。从权值大的公主开始遍历,如果她的两个王子在一个环里,那么显然她的两个王子已经被权值更大的公主选了。

但也有些需要注意的细节:

如果fa!=fb代表公主选的两个王子在不同的并查集里,但不代表她一定可以选一个,万一两个并查集都成环了呢.

同时fa!=fb,但其中一个已经成环,一个没有成环,两个并在一起,同样边数=点数,所以如果让f[fa]=fb,就让fb标记成环

CodeForces - 350B

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<vector>
using namespace std;
const int maxn=1e5+7;
struct edge{
    int to,next;
}e[maxn];
int n,m,tot,head[maxn],d[maxn],pre[maxn],ans,maxlen,len;
vector<int>b;
void add(int u,int v){
    e[tot].to=v;
    e[tot].next=head[u];
    head[u]=tot++;
}
void solve(int u,int &len){
    for(int i=head[u];i!=-1;i=e[i].next){
        int v=e[i].to;
        if(d[v]>1)continue;
        pre[u]=v;
        len++;
        solve(v,len);
    }
}
void print(int id){
    if(id==0)return;
    print(pre[id]);
    cout<<id<<' ';
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&m);
        if(m==1)b.push_back(i);
    }
    for(int i=1;i<=n;i++)
        head[i]=-1;
    for(int i=1;i<=n;i++){
        scanf("%d",&m);
        if(m!=0)add(i,m),d[m]++;
    }
    for(int i=0;i<b.size();i++){
        int u=b[i];
        len=1;
        solve(u,len);
        if(len>maxlen){
            maxlen=len;
            ans=u;
        }
    }
    cout<<maxlen<<endl;
    print(ans);
    return 0;
}
View Code

这道题就比较简单了,要求从起点到终点过程中,不能有一个点能通向两个点。

所以可以事先记录每个点的出度。

然后将终点都记录下来,遍历终点找出最长的路径

原文地址:https://www.cnblogs.com/helman/p/11962082.html