图论2 1010

最小生成树

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵恰好有need条白色边的权值和最小的生成树。题目保证有解。

对于所有数据,V,E<=100000,c为[1,1000]中的正整数。

题解

可以知道恰好选到need条白边就是最优的,考虑给所有白边加上一个值,随着值的增大,在生成树中的白边越少,所以可以二分。

取最后一次满足条件的值。

排序的时候,对于相同值的边白边优先。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=100005;
int n,m,need;
int fa[maxn],sum,cnt;
struct edge{
    int x,y,c,val;
    bool operator < (const edge a) const {
      if(val==a.val) return c<a.c;
      return val<a.val;
    }
}e[maxn],cy[maxn];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

int find(int x){return fa[x]==x ? x : fa[x]=find(fa[x]);}

bool check(int x){
    sum=0,cnt=0;
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        cy[i]=e[i];
        if(!e[i].c) cy[i].val+=x;
    }
    sort(cy+1,cy+m+1);
    for(int i=1;i<=m;i++){
        int dx=find(cy[i].x),dy=find(cy[i].y);
        if(dx!=dy){
            fa[dx]=dy;
            sum+=cy[i].val;
            cnt+=!cy[i].c;
        }
    }
    return cnt>=need;
}

int main(){
    freopen("e.in","r",stdin);
    freopen("e.out","w",stdout);
    read(n);read(m);read(need);
    for(int i=1;i<=m;i++) read(e[i].x),read(e[i].y),read(e[i].val),read(e[i].c);
    int l=-1005,r=1005,ret;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)) ret=sum-need*mid,l=mid+1;//写need 
        else r=mid-1;
    }
    printf("%d",ret);
}
e

很玄的一道题


 

贪吃蛇

 

有两条蛇(1 号蛇和 2 号蛇)在 n 行 m 列的地图上,地图上有障碍物。一条蛇碰到蛇身/障碍物/边界就会死。蛇身会不断长长——可以理解为蛇尾位置不会变,蛇只会向前伸展不会缩尾巴。两条蛇都绝顶聪明,如果自己能赢,一定会尽量快地赢;如果自己会输,一
定会死得尽量晚。给出初始局面,两蛇轮流走,每次可以且必须向上下左右移动一格。1 号蛇先走,请告诉我谁会在多少回合时赢。

对于100%的数据,1<=n<=20,1<=m<=20,0的个数不超过50个。

题解

用的是alpha-beta算法,还没怎么搞懂,就给个博客

代码T了两个点

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int inf=1000000;
const int maxn=25;
const int oo=400;
int n,m;
int mp[maxn][maxn];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
bool vis[maxn][maxn];

int maxmin(int step,int x1,int y1,int x2,int y2,int alpha,int beta){
    bool opt=false;
    if(step&1){//提高下界 
        for(int i=0;i<4;i++){
            int xx=x1+dx[i],yy=y1+dy[i],val=alpha;
            if(xx>0&&xx<=n&&yy>0&&yy<=m&&!vis[xx][yy]){
                opt=true;
                vis[xx][yy]=true;
                val=maxmin(step+1,xx,yy,x2,y2,alpha,beta);
                vis[xx][yy]=false;
            }
            if(val>alpha) alpha=val;
            if(alpha>beta) return alpha;
        }
        if(opt) return alpha;
    }
    else {//缩小上界 
        for(int i=0;i<4;i++){
            int xx=x2+dx[i],yy=y2+dy[i],val=beta;
            if(xx>0&&xx<=n&&yy>0&&yy<=m&&!vis[xx][yy]){
                opt=true;
                vis[xx][yy]=true;
                val=maxmin(step+1,x1,y1,xx,yy,alpha,beta);
                vis[xx][yy]=false;
            }
            if(val<beta) beta=val;
            if(alpha>beta) return beta;
        }
        if(opt) return beta;
    }
    if(step&1) return step-oo;
    return oo-step;
}

int main(){
    freopen("h.in","r",stdin);
    freopen("h.out","w",stdout);
    int x1,y1,x2,y2;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
     for(int j=1;j<=m;j++){
          scanf("%d",&mp[i][j]);
          if(mp[i][j]==1) {x1=i,y1=j;}
          else if(mp[i][j]==2) {x2=i,y2=j;}
          vis[i][j]=mp[i][j];
     }
    int cx=maxmin(1,x1,y1,x2,y2,-inf,inf);
    if(cx>0) printf("1 %d",oo-cx);
    else printf("2 %d",cx+oo);
}
h

信息拦截

 Alice和Bob经常利用网络传播一些不法信息。网络由n台电脑组成,Alice拥有1号电脑,Bob拥有n号电脑。有m对电脑间可以通过电缆单向传输信息。为了不被拦截,Alice每次传送信息都可以选取任意一条路径(经过的电脑可以重复),把信息沿着电缆传送给Bob。现在政府获知了这一情报,他们可以侵入恰好1台电脑,并获取经过这条电脑的所有信息。政府希望(无论Alice选择的路线如何)他们对Alice发送的每条信息都恰好获取1次(如果太少,会漏掉重要情报;如果太多,他们的电脑会被塞满的)。现在你需要求出政府可以选择入侵哪些电脑以达到要求。

对于100%的数据,2<=n<=500000,0<=m<=1000000,t<=10。

可能有自环

题解

分析一下题目要求:选取一个点满足 不属于一个点数>1的强联通分量切没有自环(使得信息不多),是1到n的路径的必经点(使得信息不漏

那么就可以理出做题步骤:1.tarjan缩点  2.判必经点。

因为不知道有向图的割点能不能用tarjan求,所以就换了《算法竞赛》上的方法:对于有向无环图,求出fs[x]起点到x的路径条数,ft[x]终点到x的路径条数,根据乘法原理:如果fs[x]*ft[x]=fs[t]那么x就是s到t的必经点。

但是路径条数增长很快,所以考虑使用hash的思想,对路径条数取模,可能会误判。

求fs和ft可以用拓扑排序来做,但是考虑不能到达n的点对整个图是有影响的,所以建新图的时候只能把能到达n的点放进去。

#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=500005;
const int maxm=1000005;
const int mod=1000000007;
int t,n,m;
int cnt,head[maxn];
int cur,low[maxn],dfn[maxn];
int top,num,s[maxn],res[maxn],mp[maxn];
bool in[maxn],flag[maxn],circle[maxn];
ll fs[maxn],ft[maxn];
struct edge{
    int x,y,next;
}e[maxm],go[maxm],back[maxm];
int cntg,cntb,headg[maxn],headb[maxn];
int g[maxn],b[maxn];


template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

void add(int x,int y){
    e[++cnt]=(edge){x,y,head[x]};
    head[x]=cnt;
}

void addg(int x,int y){
    go[++cntg]=(edge){x,y,headg[x]};
    headg[x]=cntg;
}

void addb(int x,int y){
    back[++cntb]=(edge){x,y,headb[x]};
    headb[x]=cntb;
}

void init(){
    cnt=num=cntg=cntb=cur=top=0;
    memset(g,0,sizeof(g));
    memset(b,0,sizeof(b));
    memset(fs,0,sizeof(fs));
    memset(ft,0,sizeof(ft));
    memset(mp,0,sizeof(mp));
    memset(res,0,sizeof(res));
    memset(dfn,0,sizeof(dfn));
    memset(head,0,sizeof(head));
    memset(in,false,sizeof(in));
    memset(headg,0,sizeof(headg));
    memset(headb,0,sizeof(headb));
    memset(circle,false,sizeof(circle));
}

void tarjan(int x){
    if(x==n) in[x]=true;
    dfn[x]=low[x]=++cur;
    s[++top]=x;flag[x]=true;
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].y;
        if(!dfn[y]) {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(flag[y]) low[x]=min(low[x],dfn[y]);
        in[x]|=in[y];
    }
    if(dfn[x]==low[x]){
        num++;
        int kk;
        if(s[top]==x) mp[num]=x;
        do{
            kk=s[top--];
            res[kk]=num;
            flag[kk]=false;
        }while(kk!=x);
    }
}

void back_topsort(){
    queue<int> q;
    ft[res[n]]=1;
    q.push(res[n]);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=headb[x];i;i=back[i].next){
            int y=back[i].y;
            b[y]--;
            ft[y]+=ft[x];
            if(ft[y]>=mod) ft[y]-=mod;
            if(!b[y]) q.push(y);
        }
    }
}

void go_topsort(){
    queue<int> q;
    fs[res[1]]=1;
    q.push(res[1]);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        if(mp[x]&&!circle[mp[x]]&&fs[x]*ft[x]%mod==ft[res[1]]) s[++top]=mp[x];
        for(int i=headg[x];i;i=go[i].next){
            int y=go[i].y;
            g[y]--;
            fs[y]+=fs[x];
            if(fs[y]>=mod) fs[y]-=mod;
            if(!g[y]) q.push(y);
        }
    }
}

void solve(){
    read(n);read(m);
    init();
    for(int i=1;i<=m;i++){
        int x,y;
        read(x);read(y);
        if(x==y) circle[x]=true;
        else add(x,y);
    }
    tarjan(1);
//    for(int i=1;i<=n;i++) printf("%d ",res[i]);
//    putchar(10);
    if(!dfn[n]||res[1]==res[n]) {printf("0\n\n");return ;}//没联通
    for(int x=1;x<=n;x++)
     if(dfn[x]&&in[x]){
          for(int i=head[x];i;i=e[i].next){
               int y=e[i].y;
               if(in[y]&&res[x]!=res[y]){
                    addg(res[x],res[y]);g[res[y]]++;
                    addb(res[y],res[x]);b[res[x]]++;
             }
         }
     }
    back_topsort();
    go_topsort();
    printf("%d\n",top);
    for(int i=1;i<=top;i++)
     printf("%d ",s[i]);
    putchar(10);
}

int main(){
    freopen("i.in","r",stdin);
    freopen("i.out","w",stdout);
    read(t);
    while(t--) solve();
}
/*
4
5 5
1 3
4 5
3 2
2 4 
4 2
*/
i

std是直接把新图建成无向图跑割点的。

posted @ 2019-10-11 19:22  _JSQ  阅读(163)  评论(0编辑  收藏  举报