1. 程式人生 > >國慶 day 1 下午

國慶 day 1 下午

一個數 clu 自己的 event 思路 倍增 ret closed [1]

一道圖論好題(graph)

Time Limit:1000ms Memory Limit:128MB

題目描述

LYK有一張無向圖G={V,E},這張無向圖有n個點m條邊組成。並且這是一張帶權圖,不僅有邊權還有點權。

LYK給出了一個子圖的定義,一張圖G’={V’,E’}被稱作G的子圖,當且僅當

·G’的點集V’包含於G的點集V。

·對於E中的任意兩個點a,b∈V’,當(a,b)∈E時,(a,b)一定也屬於E’,並且連接這兩個點的邊的邊權是一樣的。

LYK給一個子圖定義了它的價值,它的價值為:點權之和與邊權之和的比。 看

LYK想找到一個價值最大的非空子圖,所以它來找你幫忙啦。

輸入格式(graph.in)

第一行兩個數n,m表示一張n個點m條邊的圖。

第二行n個數ai表示點權。

接下來m行每行三個數u,v,z,表示有一條連接u,v的邊權為z的無向邊。數據保證任意兩個點之間最多一條邊相連,並且不存在自環。

輸出格式(graph.out)

你需要輸出這個價值最大的非空子圖的價值,由於它是一個浮點數,你只需要保留小數點後兩位有效數字。

輸入樣例

3 3

2 3 4

1 2 3

1 3 4

2 3 5

輸出樣例

1.67

樣例解釋

選擇1,2兩個點,則價值為5/3=1.67。

對於20%的數據n=2

對於50%的數據n<=5

對於100%的數據1<=n,m<=1000001<=ai,z<=1000。

思路:當時跑了01分數規劃,但是用的bfs找的負環,所以TLE,卡了我5個點。

技術分享
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 100010
using
namespace std; int n,m,tot; double w[MAXN],dis[MAXN]; double l=0,r=1500,ans,mid; int val[MAXN],vis[MAXN],num[MAXN]; int to[MAXN*2],net[MAXN*2],cap[MAXN*2],head[MAXN*2]; void add(int u,int v,int w){ to[++tot]=v;net[tot]=head[u];cap[tot]=w;head[u]=tot; to[++tot]=u;net[tot]=head[v];cap[tot]=w;head[v]=tot; } bool spfa(int s){ queue<int>que; memset(num,0,sizeof(num)); memset(vis,0,sizeof(vis)); memset(dis,0x3f,sizeof(dis)); que.push(s); dis[s]=0;vis[s]=1;num[s]++; while(!que.empty()){ int now=que.front(); que.pop(); vis[now]=0; for(int i=head[now];i;i=net[i]){ if(dis[to[i]]>dis[now]+w[i]){ dis[to[i]]=dis[now]+w[i]; if(!vis[to[i]]){ vis[to[i]]=1; num[to[i]]++; que.push(to[i]); if(num[to[i]]>n) return true; } } } } return false; } void work(){ for(int i=1;i<=tot;i++) w[i]=(double)cap[i]*mid-val[to[i]]; } bool check(){ for(int i=1;i<=n;i++) if(spfa(i)) return true; return false; } int main(){ freopen("graph.in","r",stdin); freopen("graph.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&val[i]); for(int i=1;i<=m;i++){ int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); } while(r-l>0.0000001){ mid=(l+r)/2; work(); if(check()){ ans=mid; l=mid; } else r=mid; } printf("%.2lf",ans*2); }
View Code

然後改成dfs找負環,AC了。

技術分享
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const double eps=1e-5;
const int mxn=100010;
int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<0 || ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0 && ch<=9){x=x*10+ch-0;ch=getchar();}
    return x*f;
}
struct edge{
    int v,nxt,w;
    double c;
}e[mxn<<1];
int hd[mxn*2],mct=0;
void add_edge(int u,int v,int w){
    e[++mct].v=v;e[mct].nxt=hd[u];hd[u]=mct;e[mct].w=w;return;
}
bool vis[mxn];
double dis[mxn];
bool SPFA(int u){
    vis[u]=1;
    for(int i=hd[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(dis[v]>dis[u]+e[i].c){
            dis[v]=dis[u]+e[i].c;
            if(vis[v] || SPFA(v)){
                vis[v]=0;return 1;
            }
        }
    }
    vis[u]=0;
    return 0;
}
int n,m;
int f[mxn];
void restore(double r){
    for(int i=1;i<=mct;i++)
        e[i].c=(double)e[i].w*r-f[e[i].v];
    return;
}
bool check(){
    for(int i=1;i<=n;i++)
        if(SPFA(i))return 1;
    return 0;
}
int main(){
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    int i,j;
    int u,v,w;
    n=read();m=read();
    for(i=1;i<=n;i++)
        f[i]=read();
    for(i=1;i<=m;i++){
        u=read();v=read();w=read();
        add_edge(u,v,w);
        add_edge(v,u,w);
    }
    double l=0,r=1500,ans;
    while(r-l>eps){
        double mid=(l+r)/2;
        restore(mid);
        if(check()){
            ans=mid; 
            l=mid;
        }else r=mid;
    }
    printf("%.2f\n",ans*2);
    return 0;
}
View Code

後來聽老師講了之後發現這是道結論題:最優解一定是一條邊+兩個點。

技術分享
#include <cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
double ans;
int A,B,C,n,m;
int a[100005];
int main(){
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&A,&B,&C);
        ans=max(ans,(a[A]+a[B])/(C+0.0));
    }
    printf("%.2f\n",ans);
    return 0;
}
View Code

拍照(photo)

Time Limit:1000ms Memory Limit:128MB

題目描述

假設這是一個二次元。

LYK召集了n個小夥伴一起來拍照。他們分別有自己的身高Hi和寬度Wi。

為了放下這個照片並且每個小夥伴都完整的露出來,必須需要一個寬度為ΣWi,長度為max{Hi}的相框。(因為不能疊羅漢)。

LYK為了節省相框的空間,它有了絕妙的idea,讓部分人躺著!一個人躺著相當於是身高變成了Wi,寬度變成了Hi。但是很多人躺著不好看,於是LYK規定最多只有n/2個人躺著。(也就是說當n=3時最多只有1個人躺著,當n=4時最多只有2個人躺著)

LYK現在想問你,當其中部分人躺著後,相框的面積最少是多少。

輸入格式(photo.in)

第一行一個數n。

接下來n行,每行兩個數分別是Wi,Hi

輸出格式(photo.out)

你需要輸出這個相框的面積最少是多少。

輸入樣例

3

3 1

2 2

4 3

輸出樣例

27

樣例解釋

如果沒人躺過來,需要27的面積。

我們只要讓第1個人躺過來,就只需要21的面積!

對於30%的數據n<=10。

對於60%的數據n<=1000Wi,Hi<=10。

對於100%的數據1<=n,Wi,Hi<=1000。

思路:考試的時候寫了個DP,但是忘了考慮他的後效性,所以掛掉了8個點。

技術分享
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,sumh,sumw;
int ans=0x7f7f7f7f;
int w[1001],h[1001];
int f[1001][501],sum[1001][501];
int maxh[1001][501],maxw[1001][501];
int main(){
    freopen("photo.in","r",stdin);
    freopen("photo.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&w[i],&h[i]);
        sumw+=w[i];
        sumh+=h[i];
        maxh[i][i]=max(w[i],maxh[i-1][i-1]);
        maxh[i][0]=max(h[i],maxh[i-1][0]);
        f[i][0]=maxh[i][0]*sumw;
        f[i][i]=maxh[i][i]*sumh;
        sum[i][0]=sumw;
        sum[i][i]=sumh;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++){
            int bns=0;
            f[i][j]=f[i-1][j];
            if(h[i]<=maxh[i-1][j]){
                f[i][j]+=w[i]*maxh[i-1][j];
                maxh[i][j]=maxh[i-1][j];
            }    
            else if(h[i]>maxh[i-1][j]){
                f[i][j]+=sum[i-1][j]*(h[i]-maxh[i-1][j])+w[i]*h[i];
                maxh[i][j]=h[i];
            }
            sum[i][j]=sum[i-1][j]+w[i];
            
            if(w[i]<=maxh[i-1][j-1])    bns+=h[i]*maxh[i-1][j-1];
            else if(w[i]>maxh[i-1][j-1])    bns+=sum[i-1][j-1]*(w[i]-maxh[i-1][j-1])+w[i]*h[i];
            if(f[i][j]>f[i-1][j-1]+bns){
                f[i][j]=f[i-1][j-1]+bns;
                maxh[i][j]=max(w[i],maxh[i-1][j-1]);
                sum[i][j]=sum[i-1][j-1]+h[i];
            }
        }
    for(int i=0;i<=n/2;i++)
        ans=min(f[n][i],ans);
    cout<<ans;
}
View Code

正解思路:枚舉高度,貪心累計寬度

技術分享
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 1005
using namespace std;
int n,ans=0x7f7f7f7f;
int w[MAXN],h[MAXN],bns[MAXN];
int cmp(int a,int b){
    return a>b;
}
int main(){
    freopen("photo.in","r",stdin);
    freopen("photo.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&h[i]);
    for(int i=1;i<=1000;i++){
        int sum=0,flag=0,cnt=0,num=0;
        for(int j=1;j<=n;j++)
            if(h[j]<=i&&(w[j]<h[j]||w[j]>i))
                sum+=w[j];
            else if(w[j]>i&&h[j]>i){
                flag=1;
                break;
            }
            else if(h[j]>i){
                cnt++;
                sum+=h[j];
            }
            else{
                bns[++num]=w[j]-h[j];
                sum+=w[j];
            }
        if(flag)    continue;
        if(cnt>n/2)    continue;
        sort(bns+1,bns+1+num,cmp);
        for(int j=1;j<=min(n/2-cnt,num);j++)
            sum-=bns[j];
        ans=min(ans,sum*i);
    }
    cout<<ans;
}
View Code

或和異或(xor)

Time Limit:2000ms Memory Limit:128MB

題目描述

LYK最近在研究位運算,它研究的主要有兩個:or和xor。(C語言中對於|和^)

為了更好的了解這兩個運算符,LYK找來了一個2^n長度的數組。它第一次先對所有相鄰兩個數執行or操作,得到一個2^(n-1)長度的數組。也就是說,如果一開始時a[1],a[2],…,a[2^n],執行完第一次操作後,會得到a[1] or a[2],a[3] or a[4] ,…, a[(2^n)-1] or a[2^n]。

第二次操作,LYK會將所有相鄰兩個數執行xor操作,得到一個2^(n-2)長度的數組,假如第一次操作後的數組是b[1],b[2],…,b[2^(n-1)],那麽執行完這次操作後會變成b[1] xor b[2], b[3] xor b[4] ,…, b[(2^(n-1))-1] xor b[2^(n-1)]。

第三次操作,LYK仍然將執行or操作,第四次LYK執行xor操作。如此交替進行。

最終這2^n個數一定會變成1個數。LYK想知道最終這個數是多少。

為了讓這個遊戲更好玩,LYK還會執行Q次修改操作。每次修改原先的2^n長度的數組中的某一個數,對於每次修改操作,你需要輸出n次操作後(最後一定只剩下唯一一個數)剩下的那個數是多少。

輸入格式(xor.in)

第一行兩個數n,Q。

接下來一行2^n個數ai表示一開始的數組。

接下來Q行,每行兩個數xi,yi,表示LYK這次的修改操作是將a{xi}改成yi。

輸出格式(xor.out)

Q行,表示每次修改操作後執行n次操作後剩下的那個數的值。

輸入樣例

2 4

1 6 3 5

1 4

3 4

1 2

1 2

輸出樣例

1

3

3

3

樣例解釋

第一次修改,{4,6,3,5}->{6,7}->{1}

第二次修改,{4,6,4,5}->{6,5}->{3}

第三次修改,{2,6,4,5}->{6,5}->{3}

第四次修改,{2,6,4,5}->{6,5}->{3}

對於30%的數據n<=17Q=1。

對於另外20%的數據n<=10,Q<=1000。

對於再另外30%的數據n<=12,Q<=100000。

對於100%的數據1<=n<=171<=Q<=10^51<=xi<=2^n,0<=yi<2^300<=ai<2^30。

思路:倍增。

技術分享
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m;
bool ok;
int a[21][200001];
int poss,v;
int main(){
    freopen("xor.in","r",stdin);
    freopen("xor.out","w",stdout);
    scanf("%d%d",&n,&m);
    int len=pow(2,n);
    for(int i=1;i<=len;i++) scanf("%d",&a[n][i]);
    for(int i=n-1;i>=0;i--){
        int l=pow(2,i);
        if((n-i)%2)
            for(int j=1;j<=l;j++)
                a[i][j]=a[i+1][(j<<1)-1]|a[i+1][(j<<1)];
        else
            for(int j=1;j<=l;j++)
                a[i][j]=a[i+1][(j<<1)-1]^a[i+1][(j<<1)];
    }
    for(int z=1;z<=m;z++){
        ok=false;int val,pos;
        scanf("%d%d",&pos,&val);
        a[n][pos]=val;
        if(pos%2) poss=pos+1;
        else{
            poss=pos;
            pos--;
        }
        for(int i=n-1;i>=0;i--)
            if((n-i)%2){
                v=a[i+1][pos]|a[i+1][poss];
                if(v==a[i][(pos>>1)+(pos-((pos>>1)<<1))]){
                    printf("%d\n",a[0][1]);
                    ok=true;
                    break;
                }
                a[i][(pos>>1)+(pos-((pos>>1)<<1))]=v;
                pos=(pos>>1)+(pos-((pos>>1)<<1));
                if(pos%2) poss=pos+1;
                else{
                    poss=pos;
                    pos--;
                }
            }
            else{
                v=a[i+1][pos]^a[i+1][poss];
                if(v==a[i][(pos>>1)+(pos-((pos>>1)<<1))]){
                    printf("%d\n",a[0][1]);
                    ok=true;
                    break;
                }
                a[i][(pos>>1)+(pos-((pos>>1)<<1))]=v;
                pos=(pos>>1)+(pos-((pos>>1)<<1));
                if(pos%2) poss=pos+1;
                else{
                    poss=pos;
                    pos--;
                }
            }
        if(!ok) printf("%d\n",a[0][1]);
    }
}
View Code

另一種思路:線段樹。

技術分享
#include<cstdio>
#include<iostream>
#define N 131073
using namespace std;
int n,m;
int sum[N<<2],mid[N<<2];
void build(int now,int l,int r,int dep){
    if(l==r){
        scanf("%d",&sum[now]);
        return;
    }
    mid[now]=(l+r)/2;
    build(now*2,l,mid[now],dep+1);
    build(now*2+1,mid[now]+1,r,dep+1);
    if((n-dep+1)&1) sum[now]=(sum[now*2]|sum[now*2+1]);
    else sum[now]=(sum[now*2]^sum[now*2+1]);
}
void change(int now,int l,int r,int pos,int w,int dep){
    if(l==r){
        sum[now]=w;
        return;
    }
    if(pos<=mid[now]) change(now*2,l,mid[now],pos,w,dep+1);
    else change(now*2+1,mid[now]+1,r,pos,w,dep+1);
    if((n-dep+1)&1) sum[now]=(sum[now*2]|sum[now*2+1]);
    else sum[now]=(sum[now*2]^sum[now*2+1]);
}
int main(){
    freopen("xor.in","r",stdin);
    freopen("xor.out","w",stdout);
    scanf("%d%d",&n,&m);
    int s=1<<n;
    build(1,1,s,1);
    while(m--){
        int x,y;
        scanf("%d%d",&x,&y);
        change(1,1,s,x,y,1);
        printf("%d\n",sum[1]);
    }
}
View Code

國慶 day 1 下午