首页 > 代码库 > USACO 2017 January Platinum
USACO 2017 January Platinum
因为之前忘做了,赶紧补上。
T1.Promotion Counting
题目大意:给定一个以1为根的N个节点的树(N<=100,000),每个节点有一个权值,对于每个节点求出权值比它大的子孙的个数。
思路:肯定先要求出dfs序,首先无脑想到主席树,后来发现只要按权值从大到小处理就不用那么麻烦了。
#include<cstdio> #include<algorithm> using namespace std; char B[1<<26],*S=B,C;int X; inline int read() { while((C=*S++)<‘0‘||C>‘9‘); for(X=C-‘0‘;(C=*S++)>=‘0‘&&C<=‘9‘;)X=(X<<3)+(X<<1)+C-‘0‘; return X; } #define MN 100000 #define lb(x) (x&-x) struct edge{int nx,t;}e[MN+5],p[MN+5]; int h[MN+5],en,d[MN+5],o[MN+5],cnt,s[MN+5],ans[MN+5]; bool cmp(edge a,edge b){return a.t>b.t;} inline void ins(int x,int y){e[++en]=(edge){h[x],y};h[x]=en;} void pre(int x) { d[x]=++cnt; for(int i=h[x];i;i=e[i].nx)pre(e[i].t); o[x]=cnt; } inline void inc(int x){for(;x<=MN;x+=lb(x))++s[x];} inline int sum(int x){int r=0;for(;x;x-=lb(x))r+=s[x];return r;} int main() { fread(B,1,1<<26,stdin); int n=read(),i; for(i=1;i<=n;++i)p[i]=(edge){i,read()}; sort(p+1,p+n+1,cmp); for(i=2;i<=n;++i)ins(read(),i); pre(1); for(i=1;i<=n;++i) { ans[p[i].nx]=sum(o[p[i].nx])-sum(d[p[i].nx]); inc(d[p[i].nx]); } for(i=1;i<=n;++i)printf("%d\n",ans[i]); }
T2.Building a Tall Barn
题目大意:给定长度为N的序列ai,对每个ai分配ci使得ci>0且ci之和等于K,求出最小的ai/ci之和。(N<=100,000,K<=10^12)
思路:容易想到先给每个ai分1,用堆维护每个ai再多分1能获得的收益,O(KlogN)实现。也就相当于N堆物品,从中取走K个求最大收益,我们二分一个取的下界,把大于这个下界的都取走,看看取了几个就可以了,取几个我一开始也二分,T了,然后发现可以数学直接算。另外这题精度要求可能比较大,二分多分几次就行了,最后O(NlogK)。从题解中学到了种二分写法(虽然好像以前见识过):for(i=1;i<=100;++i)if(check(mid))...
#include<cstdio> #include<cmath> #include<iostream> using namespace std; #define ll long long char B[1<<26],*S=B,C;ll X; inline ll read() { while((C=*S++)<‘0‘||C>‘9‘); for(X=C-‘0‘;(C=*S++)>=‘0‘&&C<=‘9‘;)X=(X<<3)+(X<<1)+C-‘0‘; return X; } #define MN 100000 ll k,a[MN+5]; ll cal(double x){return (ll)((sqrt(1+4*x)-1)/2);} int main() { fread(B,1,1<<26,stdin); int n=read(),i;k=read()-n;double l,r,mid;ll cnt; for(i=1;i<=n;++i)a[i]=read(); for(l=0,r=1e12;r-l>1e-12;) { mid=(l+r)/2; for(cnt=0,i=1;i<=n;++i)cnt+=cal(a[i]/mid); if(cnt<=k)r=mid;else l=mid; } for(i=1,l=0;i<=n;++i)l+=a[i]/(double)(cal(a[i]/r)+1); cout<<(ll)(l+0.5); }
T3.Subsequence Reversal
题目大意:给定一个长度为N的序列,允许翻转一个子序列,求最长不下降子序列长度。(N<=50,序列元素在1..50内)
思路:一眼看上去很不可做,而且N这么小很吓人,仔细想想可以搞个类似区间DP,从整个序列开始,左边一个元素不翻或右边一个元素不翻或交换这两个元素,逐渐把区间缩小,然后再在状态里加上能取的数字区间,还是挺好转移的,复杂度O(N^2*maxai^2),另外题目里明明写的increasing却是不下降,很坑爹啊。
#include<cstdio> #include<algorithm> using namespace std; char B[1<<26],*S=B,C;int X; inline int read() { while((C=*S++)<‘0‘||C>‘9‘); for(X=C-‘0‘;(C=*S++)>=‘0‘&&C<=‘9‘;)X=(X<<3)+(X<<1)+C-‘0‘; return X; } #define MN 50 int a[MN+5],f[MN+5][MN+5][MN+5][MN+5],ans; inline void r(int&a,int b){if(b>a){a=b;if(b>ans)ans=b;}} int main() { fread(B,1,1<<26,stdin); int n=read(),i,j,k,l; for(i=1;i<=n;++i)a[i]=read(); for(i=1;i<=n;++i)for(j=n;j>=i;--j) for(k=1;k<=MN;++k)for(l=MN;l>=k;--l) { if(k<=a[i]&&a[i]<=l) r(f[i+1][j][a[i]][l],f[i][j][k][l]+1), r(f[i+1][j-1][k][a[i]],f[i][j][k][l]+1); if(k<=a[j]&&a[j]<=l) r(f[i][j-1][k][a[j]],f[i][j][k][l]+1), r(f[i+1][j-1][a[j]][l],f[i][j][k][l]+1); if(i<j&&k<=a[j]&&a[j]<=a[i]&&a[i]<=l) r(f[i+1][j-1][a[j]][a[i]],f[i][j][k][l]+2); r(f[i+1][j][k][l],f[i][j][k][l]); r(f[i][j-1][k][l],f[i][j][k][l]); r(f[i][j][k+1][l],f[i][j][k][l]); r(f[i][j][k][l-1],f[i][j][k][l]); } printf("%d",ans); }
USACO 2017 January Platinum