Noip 备战篇(四)

来源:互联网 发布:php限制ip访问次数 编辑:程序博客网 时间:2024/06/06 01:47

【最小生成树】 new oj 1802
题目大意:
给定一个边带正权的连通无向图G=(V,E),其中N=|V|,M=|E|,N个点从1到N依次编号,给定三个正整数u,v,和L (u≠v),假设现在加入一条边权为L的边(u,v),那么需要删掉最少多少条边,才能够使得这条边既可能出现在最小生成树上,也可能出现在最大生成树上?

思路:

这题在考场上已经A了,最小割解决。

#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<algorithm>#include<string>#include<cstring>#include<queue>#define F(i,begin,end) for(int i=begin;i<=end;i++)using namespace std;const int imax=1000+229;const int dmax=4000+229;const int bmax=4000000+229;const int inf=1000000229;int n,m,k,a[imax],b[imax];int num,head[dmax],to[bmax],re[bmax],next[bmax];int ans,S,T;void iadd(int u,int v,int flow){    to[num]=v;  re[num]=flow;    next[num]=head[u]; head[u]=num++;}void add(int u,int v,int flow){    iadd(u,v,flow);    iadd(v,u,0);}void iread(){    scanf("%d",&n);    S=0; T=3229;    memset(head,-1,sizeof(head));    for(int i=1;i<=n;i++)    {         scanf("%d",&a[i]); ans+=a[i];         add(S,1000+i,a[i]);    }    for(int i=1;i<=n;i++)    {         scanf("%d",&b[i]); ans+=b[i];         add(1000+i,T,b[i]);         }    scanf("%d",&m);    int cl1,cl2;    int now,now2;    for(int i=1;i<=m;i++)    {        scanf("%d%d%d",&k,&cl1,&cl2);        now=i;          now2=2000+i; ans+=(cl1+cl2);        add(S,now,cl1); add(now2,T,cl2);         int x;        for(int j=1;j<=k;j++)         {            scanf("%d",&x);            add(now,1000+x,inf);            add(1000+x,now2,inf);        }    }       }int d[dmax];queue<int> q;bool BFS(){   memset(d,0,sizeof(d));   q.push(S);   d[S]=1;   while(!q.empty())   {       int u=q.front();       q.pop();       for(int k=head[u];k!=-1;k=next[k])       {           if(re[k] && !d[to[k]])           {              d[to[k]]=d[u]+1;              q.push(to[k]);                   }               }           }   return d[T]>0;    }int DFS(int x,int c){   if(x==T || c==0) return c;   int r=c,f;   for(int k=head[x];k!=-1;k=next[k])   {        if(re[k] && d[to[k]]==d[x]+1)        {           f=DFS(to[k],min(re[k],r));           r-=f; re[k]-=f;  re[k^1]+=f;            if(r==0) break;        }          }        if(r==c) d[x]=0; // 当前节点无法继续增广;    return c-r; }void iwork(){    int kk=0;    while(BFS()) { kk+=DFS(S,inf); }        printf("%d\n",ans-kk);}int main(){    iread();    iwork();    return 0;}

【最小生成树】 new oj 1968
题目大意:
二维平面上有n个点(xi, yi),现在这些点中取若干点构成一个集合S,对它们按照x坐标排序,顺次连接,将会构成一些连续上升、下降的折线,设其数量为f(S)。如下图中,1->2,2->3,3->5,5->6(数字为下图中从左到右的点编号),将折线分为了4部分,每部分连续上升、下降。
现给定k,求满足f(S) = k的S集合个数。
(n <= 50000,0 < k <= 10)

思路:

  • 首先不难想出暴力方程:
  • f[i][j][0/1]表示以第i 个点结尾,折线为k,最后一条是上升或者下降。
  • 转移方程:
  • for(int len=1;len<=k;len++)
    for(int j=0;j < i;j++)
  • f[i][len][0]+=f[j][len][0]+f[j][len-1][1];
  • f[i][len][1]+=f[j][len][1]+f[j][len-1][0];

    -紧接着可以想到前面把j的循环去掉,这就可以用树状数组了。如果你把纵坐标排个序建立树状数组就可以解决,详细见代码:

#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<algorithm>#include<string>#include<cstring>#define F(i,begin,end) for(int i=begin;i<=end;i++)using namespace std;typedef long long LL;const int mod=100007; const int imax=50000+229;int n,k,L;struct Point{    int x,y;    }p[imax];LL ans,f[imax<<1][12][2];LL temp[imax<<1][12][2]; bool cmp(const Point a,const Point b){ return (a.x<b.x)||(a.x==b.x && a.y<b.y);}void iread(){    L=0;    scanf("%d%d",&n,&k);    for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y),L=max(L,p[i].y);     sort(p+1,p+n+1,cmp);    L+=1;}void add(int x,int j,int k,LL v){    for(int i=x;i<=L;i+=(i&(-i)))        temp[i][j][k]=(temp[i][j][k]+v)%mod;}LL query(int x,int j,int k){    LL sum=0;    for(int i=x;i>0;i-=(i&(-i)))        sum=(sum+temp[i][j][k])%mod;    return sum;}void iwork(){    ans=0;    for(int i=1;i<=n;i++)    {        f[i][0][0]=1; f[i][0][1]=1;        add(p[i].y,0,0,1);        add(p[i].y,0,1,1);        for(int len=1;len<=k;len++)        {            LL res1=query(p[i].y-1,len,0)+query(p[i].y-1,len-1,1);            res1=(res1+mod)%mod;            LL res2=query(L,len,1)-query(p[i].y,len,1)+query(L,len-1,0)-query(p[i].y,len-1,0);            res2=(res2+mod)%mod;        //  printf("%d:%lld %lld\n",i,res1,res2);            f[i][len][0]+=res1;            f[i][len][1]+=res2;            add(p[i].y,len,0,f[i][len][0]);            add(p[i].y,len,1,f[i][len][1]);        }     }    for(int i=1;i<=n;i++)    {       //  printf("%lld %lld==\n",f[i][k][0],f[i][k][1]);         ans=(ans+f[i][k][0]+f[i][k][1])%mod;    }       printf("%lld\n",ans);}   int main(){    iread();    iwork();    return 0;}

【折线编号】 new oj 1870
题目大意:
现在一共有N台机器,从1到N,编号与机器一一对应。显然一共有N!种编号。然而搞出太大的动静不好,所以新的编号要求第I台机器的编号小于第I+2台机器和第I+3台机器的编号,当然如果不存在I+2,I+3,也就不用管了。
现在你的任务是求出对N台机器,有多少种满足限制的编号方案。
(0< N<=10^18)

思路: 看到了不是数论就是快速幂了。实际上看答案可以想到是一个斐波那契数列。证明如下:
显然a[i-2]

#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<algorithm>#include<string>#include<cstring>#define F(i,begin,end) for(int i=begin;i<=end;i++)using namespace std;typedef long long LL;const int p=10000001; LL base[2][2];LL n; LL a[2];void Maxtrix1(){    LL temp[2];    temp[0]=temp[1]=0;    for(int i=0;i<2;i++)        for(int j=0;j<2;j++) temp[i]=(temp[i]+a[j]*base[j][i])%p;    for(int i=0;i<2;i++) a[i]=temp[i];   }void Maxtrix2(){    LL temp[2][2];    for(int i=0;i<2;i++)         for(int j=0;j<2;j++) temp[i][j]=0;    for(int i=0;i<2;i++)        for(int j=0;j<2;j++)            for(int z=0;z<2;z++) temp[i][j]=(temp[i][j]+base[i][z]*base[z][j])%p;    for(int i=0;i<2;i++)         for(int j=0;j<2;j++) base[i][j]=temp[i][j];  }int main(){    scanf("%lld",&n);    a[0]=1; a[1]=2;    base[0][0]=0; base[0][1]=1;    base[1][0]=1; base[1][1]=1;    if(n<=2) printf("%lld\n",a[n-1]);    else    {        n-=LL(2);        while(n)        {            if(n&1) Maxtrix1();            Maxtrix2();            n>>=1;        }        printf("%lld\n",a[1]);    }    return 0;}
0 0
原创粉丝点击