[APIO2016]
2016的題貌似是韓國棒子出的,好喪啊.... 看了題解還想了好久......
-------------------------------------------------
A.Boat
有n個數,每個數字可取[li,ri]內的任意整數si,但是要求對于任意i<j,都有si<sj,求方案數 ?n<=500,l,r<=10^9
題解:首先離散,然后不同區間的方案數很好轉移,我們考慮相同區間的方案數,發現是一個差分了多次的數列,如果區間長度是l,選m個這樣的區間,那么方案數是11111...差分m次后的第l項。然后我們可以發現這個其實是一個組合數,等于C(m,m+l)。我們用f[i][j]表示第i個選第j個區間的方案數,然后我們把f數組前綴和之后推出公式,
f[i][j]=∑C(i-i'-1,i-i'+l-1) * f[i'-1][j-1]
復雜度n^3
#include<iostream> #include<cstdio> #include<algorithm> #define mod 1000000007 #define MAXN 1000 #define int long long using namespace std; inline 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; }int tot=-1,n,L[2*MAXN+5]; int l2[MAXN*2+5],l[MAXN+5],r[MAXN+5]; int f[MAXN+5][MAXN*2+5]; int inv[MAXN+5],p[MAXN+5];main() {p[0]=inv[0]=p[1]=inv[1]=1;for(int i=2;i<=MAXN;i++){p[i]=1LL*p[i-1]*i%mod;inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;}//for(int i=2;i<=MAXN;i++) inv[i]=1LL*inv[i]*inv[i-1]%mod;n=read();for(int i=1;i<=n;i++){l[i]=l2[i*2-1]=read();r[i]=l2[i<<1]=read();l2[i<<1]+=1;}sort(l2+1,l2+n*2+1);for(int j=1;j<=n*2;j++)if(l2[j]!=l2[j-1])l2[++tot]=l2[j]; tot++;for(int i=1;i<tot;i++) L[i]=l2[i]-l2[i-1]; for(int i=1;i<=n;i++){ l[i]=upper_bound(l2,l2+tot,l[i])-l2;r[i]=upper_bound(l2,l2+tot,r[i])-l2;// cout<<l[i]<<" "<<r[i]<<endl; }for(int i=0;i<tot;i++) f[0][i]=1;for(int i=1;i<=n;i++){f[i][0]=1;for(int j=l[i];j<=r[i];j++){f[i][j]=(long long)L[j]*f[i-1][j-1]%mod;//cout<<f[i][j]<<endl;int now=1;long long c=L[j]-1;for(int k=i-1;k;--k)if(l[k]<=j&&j<=r[k]){now++;c=c*(long long)(L[j]+now-2)%mod*inv[now]%mod;if(!c)break;f[i][j]=(f[i][j]+(long long)f[k-1][j-1]*c)%mod;}}for(int j=1;j<tot;j++)f[i][j]=((long long)f[i][j]+f[i-1][j]+f[i][j-1]-f[i-1][j-1]+mod)%mod;// for(int j=1;j<tot;j++)// cout<<i<<" "<<j<<" "<<f[i][j]<<endl; }cout<<(long long)(f[n][tot-1]-1+mod)%mod;return 0; }B.給定一棵n個非葉節點,m個葉節點的樹,有邊權,定義修改邊權的費用為前后邊權的差的絕對值,你要讓所有葉節點到根節點的距離相同,但又不能把邊權改成負數,求最小費用。
n,m<=300000
題解:我們用f[i][j]表示第i個點,子樹中的葉節點到它的距離都是j的最小費用,那么f[i][0]=∑Wjk ? (jk都在子樹i中)。
很顯然,f函數是一個下凸的函數,并且存在一些拐點,拐點前后斜率變化是1,但是拐點可以重在某一個點上。只有一個葉節點時,拐點有兩個,且都為于0,凸殼形狀像一個絕對值函數。
所以我們只要知道所有拐點,就可以知道這個函數啦。
我們考慮向一個子樹添加邊時候的影響,由于w可以無限增大,在一定大小后只修改這一條邊一定最優,斜率肯定都是1,所以對于斜率大于1的部分我們都可以舍去,即彈掉所有原來斜率大等0的拐點。
這樣一次合并我們實際上只把它向右平移了一下。需要刪除和合并操作,所以寫一個可并堆就好啦。
復雜度(n+m)log(n+m)
#include<iostream> #include<cstdio> #define MN 600000 #define ll long long using namespace std; inline 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; }int n,m,fa[MN+5],in[MN+5]; ll w[MN+5],ans=0;struct Heap{Heap *l,*r;ll p;int d;Heap(ll _p):p(_p),l(0),r(0),d(1){};inline friend int dis(Heap*x){return x?x->d:0;}friend Heap* Merge(Heap*x,Heap*y){if(!x) return y;if(!y) return x;if(x->p<y->p) swap(x,y);x->r=Merge(x->r,y);if(dis(x->r)>dis(x->l)) swap(x->l,x->r);x->d=dis(x->r)+1;return x;} }*s[MN+5],*a,*b;int main() {n=read();m=read();n+=m;for(int i=2;i<=n;i++){++in[fa[i]=read()];ans+=(w[i]=read());}for(int i=n;i>1;i--){if(!s[i]) s[i]=Merge(new Heap(0),new Heap(0));for(int j=1;j<in[i];j++)s[i]=Merge(s[i]->l,s[i]->r);a=new Heap(s[i]->p+w[i]);s[i]=Merge(s[i]->l,s[i]->r);b=new Heap(s[i]->p+w[i]);s[i]=Merge(s[i]->l,s[i]->r);s[fa[i]]=Merge(s[fa[i]],Merge(s[i],Merge(a,b)));}int top=0;while(s[1])w[++top]=s[1]->p,s[1]=Merge(s[1]->l,s[1]->r);w[top+1]=0;for(int i=top;m;m--,i--) ans-=(w[i]-w[i+1])*m;printf("%lld\n",ans);return 0; }C.Gap
給定一個長度為n的嚴格遞增數列,你每次可以詢問一個數字區間的最大值,最小值,求最大差分。n<=100000
題解:對于subtask1,詢問次數不超過(n+1)/2,我們枚舉左右節點查,然后縮短這個區間就好啦。
對于subtask2,詢問的區間含有k個數時費用是k+1,要讓費用不超過3n。我們先求最大值x和最小值y,顯然答案不會低于(y-x)/(n-1),所以我們把數字分塊,每塊內不存在答案,都詢問一次就行了。
#include "gap.h" #include<algorithm> #include<iostream> using namespace std; #define ll long long #define INF 1000000000000000000LL ll s[200005]; ll ans=0; int cnt=0;ll solve(int x) {ll l,r;MinMax(0,INF,s+1,s+x);cnt=2;int i=2,j=x-1;for(l=s[1]+1,r=s[x]-1;i<=j;l=s[i++]+1,r=s[j--]-1){MinMax(l,r,s+i,s+j);}for(int i=2;i<=x;i++)ans=max(ans,s[i]-s[i-1]);return ans; }ll findGap(int T, int N) {if(T==1) return solve(N);if(N==1) return 0;MinMax(0,INF,s+1,s+2);cnt=2;ll p=(s[2]-s[1]-1)/(N-1)+1;for(ll i=s[1]+1;i<=s[2]-1;i+=p){MinMax(i,min(i+p-1,s[2]-1),s+cnt+1,s+cnt+2);if(s[cnt+1]>0)cnt+=2;}sort(s+1,s+cnt+1);for(int i=2;i<=cnt;i++)ans=max(ans,s[i]-s[i-1]);return ans; }?
轉載于:https://www.cnblogs.com/FallDream/p/apio2016.html
總結
以上是生活随笔為你收集整理的[APIO2016]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用git了解代码编写过程
- 下一篇: 介绍TCP/udp比较好的博客