AmazingCounters.com

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);
}

 

posted on 2017-02-16 09:54  ditoly  阅读(342)  评论(0编辑  收藏  举报