Noip 備戰篇(四)

【最小生成樹】 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;
}
發佈了32 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章