國慶 day 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<=100000,1<=ai,z<=1000。
思路:當時跑了01分數規劃,但是用的bfs找的負環,所以TLE,卡了我5個點。
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 100010 usingView Codenamespace 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); }
然後改成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<=1000,Wi,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<=17,Q=1。
對於另外20%的數據n<=10,Q<=1000。
對於再另外30%的數據n<=12,Q<=100000。
對於100%的數據1<=n<=17,1<=Q<=10^5,1<=xi<=2^n,0<=yi<2^30,0<=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 下午