zoukankan      html  css  js  c++  java
  • uoj Goodbye Dingyou

    uoj的题目都挺好的。

    A.新年的XOR

    观察性质,$O(1)$的。

    #include <bits/stdc++.h>
    #define for1(a,b,i) for(int i=a;i<=b;++i)
    #define FOR2(a,b,i) for(int i=a;i>=b;--i)
    using namespace std;
    typedef long long ll;
    inline int read() {
        int f=1,sum=0;
        char x=getchar();
        for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1;
        for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0';
        return f*sum;
    }
    
    int main () {
        int Test_=read();
        while (Test_--) {
            ll n;
            cin>>n;
            if(n==0) puts("8 67");
            else if(n==1) puts("2 3");
            else if(n==2) puts("3 9");
            else if(n==3) puts("3 7");
            else if(n==4) puts("97 100");
            else if(n%4==0) printf("%lld %lld
    ",n-4,n);
            else if(n%4==1) printf("2 %lld
    ",n-1);
            else if(n%4==2) printf("2 %lld
    ",n);
            else printf("%lld %lld
    ",n-3,n-1);
        }
    }
    View Code

    B.新年的叶子

    需要分析树的直径的性质。

    我们需要证明树的每一条直径都会相交与某一条直径的中点。

    反证法证明长度会变长即可。

    设我们找到的点为$root$。

    显然我们可以将叶子分成$root$不同儿子的块。

    若直径的长度为偶数,则只有$dep$(点到$root$的距离)为$frac{len}{2}$的点有用。

    否则,$dep$为可以为$frac{len}{2}$或$frac{len}{2}-1$。

    对于第一种情况,我们可以分出$a_{i}$代表第$i$个儿子的有用节点个数,还剩下一些没用的节点。

    对于第二种情况,显然长度为$frac{len}{2}$的点属于一个儿子,那么我们可以合并所有长度为$frac{len}{2}-1$的点。

    这样就将模型转化成了给定$n$个$a_{i}$和$m$个没用的点,每次选出一个点标记为黑色。

    可以重复选,知道只剩下一个集合有未标记的点的期望步数。

    我们枚举最后剩下那个集合,计算贡献。

    设这个集合的$a_{i}$为$t$,$sum a_{i}=sum$,$m$个无用点,则:

    $sum_{i=1}^{t}sum_{j=0}^{m}(sum-i+j)!*frac{sum-t}{sum-i+j}inom{t}{i}*inom{m}{j}*f[sum-i+j]*frac{(m-j+i)!}{(sum+m)!}$

    $i,j$分别是这个集合剩下点的个数以及插入的无用的点的个数。

    $f[i]$代表从0走到$i$个点的期望步数,显然为$sum_{i=1}^{x}frac{n}{n-i+1}$。

    这样过不了,然后我们发现$j-i$是一个定值我们只需要化简如下式子:

    $sum_{i=0}^{n}inom{n}{i}*inom{m}{c+i}=sum_{i=0}^{n}inom{n}{n-i}*inom{m}{c+i}=inom{n+m}{n+c}$

    然后就变成线性的做法了!

    UPD:我傻了,上述我的推导是$O(n*sqrt(n))$的算法,只不过出题人没有卡,我自己hack了自己。。。

    我真的好傻啊,其实无用点是不用管的,因为他不管选没选都可以再选,这样就可以理解为选第useless+i个点。

    然后就把那个难处理的无用点个数就抛弃了!我好傻啊。注释掉的就是原来的代码。

    #include <bits/stdc++.h>
    #define for1(a,b,i) for(int i=a,end_=b;i<=end_;++i)
    #define FOR2(a,b,i) for(int i=a,end_=b;i>=end_;--i)
    using namespace std;
    typedef long long ll;
    
    #define M 1000005
    #define mod 998244353
    int n,cnt;
    int f[M],js[M],ni[M],inv[M];
    int pos,root,e_size,head[M],du[M],dep[M],Max[M];
    
    vector <int> sta;
    
    struct node {int v,nxt;}e[M*2];
    
    inline void e_add(int u,int v) {
        e[++e_size]=(node){v,head[u]};
        head[u]=e_size;
    }
    
    inline int qpow(int x,int ci) {
        int sum=1;
        for(;ci;ci>>=1,x=1ll*x*x%mod)
            if(ci&1) sum=1ll*x*sum%mod;
        return sum;
    }
    
    inline int get_C(int x,int y) {
        if(x<y) return 0;
        return 1ll*js[x]*ni[y]%mod*ni[x-y]%mod;
    }
    
    inline void inc(int &x,int y) {x+=y,x-=x>=mod?mod:0;}
    
    inline void dfs(int x,int fa,int now) {
        dep[x]=Max[x]=dep[fa]+1;
        cnt+=now==dep[x]&&du[x]==1;
        for(int i=head[x];i;i=e[i].nxt) {
            int v=e[i].v;
            if(v==fa) continue;
            dfs(v,x,now);
            Max[x]=max(Max[x],Max[v]);
        }
    }
    
    int main () {
        //freopen("a.in","r",stdin);
        scanf("%d",&n);
        for1(2,n,i) {
            int x,y;
            scanf("%d%d",&x,&y);
            ++du[x],++du[y];
            e_add(x,y),e_add(y,x);
        }
        dfs(1,0,0);
        for1(1,n,i) if(dep[i]>dep[pos]) pos=i;
        dfs(pos,0,0);
        for1(1,n,i) if(Max[i]>Max[root]&&(Max[i]+1>>1)==dep[i]) root=i;
        
        if(Max[pos]&1) {
            dfs(root,0,0);
            pos=0;
            for1(1,n,i) if(dep[i]>dep[pos]) pos=i;
            for(int i=head[root];i;i=e[i].nxt) {
                int v=e[i].v;
                cnt=0;
                dfs(v,root,dep[pos]);
                if(cnt) sta.push_back(cnt);
            }
        }
        else {
            dfs(root,0,0);
            pos=0;
            for1(1,n,i) if(dep[i]>dep[pos]) pos=i;
            
            cnt=0;
            dfs(root,0,dep[pos]);
            sta.push_back(cnt);
            cnt=0;
            dfs(root,0,dep[pos]-1);
            sta.push_back(cnt);
        }
        
        js[0]=1;
        for1(1,n,i) js[i]=1ll*i*js[i-1]%mod;
        ni[n]=qpow(js[n],mod-2);
        FOR2(n,1,i) ni[i-1]=1ll*i*ni[i]%mod;
        for1(1,n,i) inv[i]=1ll*ni[i]*js[i-1]%mod;
        
        /*
        int ans=0;
        int sum=0,tot_x=0;
        for1(1,n,i) tot_x+=du[i]==1;
        for1(1,sta.size(),i) sum+=sta[i-1];
        for1(1,tot_x,i) inc(f[i]=f[i-1],1ll*tot_x*inv[tot_x-i+1]%mod);
        tot_x-=sum;
        
        for1(1,sta.size(),i) {
            int sel=0;
            int t=sta[i-1];
            for1(-t,tot_x-1,j) {
                sel=get_C(t+tot_x,t+j);
                if(j>=0) inc(sel,mod-get_C(tot_x,j));
                inc(ans,1ll*(sum-t)*js[sum+j]%mod*inv[sum+j]%mod*f[sum+j]%mod*js[tot_x-j]%mod*sel%mod);
            }
        }
        ans=1ll*ans*ni[sum+tot_x]%mod;
        cout<<ans<<endl;
        */
        
        int ans=0;
        int sum=0,tot_x=0;
        for1(1,n,i) tot_x+=du[i]==1;
        for1(1,sta.size(),i) sum+=sta[i-1];
        
        for1(1,sum,i) inc(f[i]=f[i-1],1ll*tot_x*inv[sum-i+1]%mod);
        
        for1(1,sta.size(),i) {
            int t=sta[i-1];
            for1(1,t,j) 
                inc(ans,1ll*js[sum-j-1]*(sum-t)%mod*f[sum-j]%mod*get_C(t,j)%mod*js[j]%mod*ni[sum]%mod);
        }
        cout<<ans<<endl;
    }
    View Code

     C.新年的叶子

    一眼上去simpson积分,然后只拿了50分。

    random_shuffle了一下之后拿了80分。

    感觉还有很多优化的余地,比如说最后一个点就不用积分了,直接解出区间即可。

    正解非常巧妙,将一个小数分成了整数部分和小数部分。

    对于$li=ri$我们直接令他为一个确定的解,否则在$[li,ri-1]$中枚举整数部分。

    另一个数为$p_{i}+q_{i}$,$p_{i}$为整数部分。则限制条件如下:

    $q_{i}-q_{j}geq a_{i,j}-p_{i}+p_{j}$

    设后面的整数部分为$c$。

    若$c>0$显然无解。

    若$c<0$显然没有限制。

    若$c=0$才会有$q_{i}>q_{j}$的限制。

    这样就好做了,直接dp出满足条件的排列个数即可。

    #include <bits/stdc++.h>
    #define for1(a,b,i) for(int i=a,end_=b;i<=end_;++i)
    #define FOR2(a,b,i) for(int i=a,end_=b;i>=end_;--i)
    using namespace std;
    typedef long long ll;
    inline int read() {
        int f=1,sum=0;
        char x=getchar();
        for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1;
        for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0';
        return f*sum;
    }
    
    #define M 10
    #define N 40
    int n,p[M];
    double ans;
    int f[N],l[M],r[M],a[M][M];
    
    inline double calc_() {
        int to[M]={0};
        for1(1,n,i) for1(1,n,j) if(i!=j) {
            int x=a[i][j]-p[i]+p[j];
            bool t[2]={l[i]==r[i],l[j]==r[j]};
            if(x<0) continue;
            if(x>0) return 0;
            if(t[0]&&!t[1]) return 0;
            if(!t[0]&&!t[1]) to[i]|=1<<j-1;
        }
        
        memset(f,0,sizeof(f));
        f[0]=1;
        for1(1,(1<<n)-1,i) {
            bool vis=0;
            for1(1,n,j) if((i>>j-1&1)&&(l[j]==r[j])) vis=1;
            if(vis) continue;
            for1(1,n,j) if(i>>j-1&1) {
                int t=i^(1<<j-1);
                if((t&to[j])!=to[j]) continue;
                f[i]+=f[t];
            }
        }
        
        int cnt=0,all=(1<<n)-1;
        for1(1,n,i) if(l[i]==r[i]) ++cnt,all^=1<<i-1;
        
        double sum=f[all];
        for1(1,n-cnt,i) sum/=i;
        return sum;
    }
    
    inline void dfs(int x) {
        if(x==n+1) return ans+=calc_(),void();
        if(l[x]==r[x]) {
            p[x]=l[x];
            dfs(x+1);
        }
        else {
            for1(l[x],r[x]-1,i) {
                p[x]=i;
                dfs(x+1);
            }
        }
    }
    
    int main() {
        n=read();
        for1(1,n,i) l[i]=read(),r[i]=read();
        for1(1,n,i) for1(1,n,j) a[i][j]=read();
        for1(1,n,i) if(a[i][i]>0) return puts("0.00000000"),0;
        
        dfs(1);
        for1(1,n,i) if(l[i]!=r[i]) ans/=r[i]-l[i];
        printf("%.8f
    ",ans);
    }
    View Code

    D.新年的代码

    想到了AGC027E的思路。

    但是无法证明为什么分段一定是最优的以及没有发现一段的贡献为$n-1$或$n$。

    首先我们考虑变换在模3意义下是不变的。

    性质1:对于一个串S,我们可以花费最多n步使得前n-1个字符变成目标字符串。

    数学归纳法,花费n步都是因为最后剩下两个字符花费了两步,例如$ab$到$ba$。

    这时我们可以省下这两步,让最后三个花费3的代价,总步数为$n-1-2+3=n$。

    性质2:对于串$s1s2,t1t2$。若$s1=t1,s2=t2$,则合并不优。

    这里$=$定义为长度相等且$mod$3之后相等。

    因为合并我们至少要在交界处操作一次,次数:$1+n1+n2-1=n1+n2$。

    然后我们就可以check一段是否可以用n-1次操作来弄,否则为n次。

    因为操作为n-1次,所以$forall i,i+1$都要被操作一次。

    这个非常好证,如果不是这样则说明可以分更小的段,与前提不符。

    这样影响的就只是操作顺序了。

    然后就可以直接dp了,$f[i][a][b]$代表$1-i-1$都相等,第$i$个字符为$a,b$是否可以。

    #include <bits/stdc++.h>
    #define for1(a,b,i) for(int i=a,end_=b;i<=end_;++i)
    #define FOR2(a,b,i) for(int i=a,end_=b;i>=end_;--i)
    using namespace std;
    typedef long long ll;
    inline int read() {
        int f=1,sum=0;
        char x=getchar();
        for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1;
        for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0';
        return f*sum;
    }
    
    #define M 500005
    int n;
    char s[M],t[M];
    bool f[M][3][3];
    
    inline int st(char x) {
        if(x=='G') return 1;
        return x=='B'?0:2;
    }
    
    inline bool check(int l,int r) {
        memset(f[l],0,sizeof(f[l]));
        f[l][s[l]][t[l]]=1;
        for1(l,r-1,i) {
            memset(f[i+1],0,sizeof(f[i]));
            for1(0,2,j) for1(0,2,k) {
                if(!f[i][j][k]) continue;
                if(s[i+1]!=k) f[i+1][(j+s[i+1]-k+3)%3][t[i+1]]=1;
                if(t[i+1]!=j) f[i+1][s[i+1]][(k+t[i+1]-j+3)%3]=1;
            }
        }
        for1(0,2,i) if(f[r][i][i]) return 1;
        return 0;
    }
    
    int main() {
        //freopen("a.in","r",stdin);
        n=read();
        scanf("%s%s",s+1,t+1);
        for1(1,n,i) s[i]=st(s[i]),t[i]=st(t[i]);
        
        int ans=0;
        for(int l=1,r;l<=n;l=r+1) {
            r=l;
            int c[2]={s[l],t[l]};
            while (c[0]!=c[1]) {
                ++r;
                c[0]=(c[0]+s[r])%3;
                c[1]=(c[1]+t[r])%3;
            }
            if(l==r) continue;
            ans+=r-l+1-check(l,r);
        }
        printf("%d
    ",ans);
    }
    View Code
  • 相关阅读:
    POJ NOI MATH-7650 不定方程求解
    POJ NOI MATH-7656 李白的酒
    POJ NOI MATH-7654 等差数列末项计算
    POJ NOI MATH-7827 质数的和与积
    POJ NOI MATH-7830 求小数的某一位
    POJ NOI MATH-7833 幂的末尾
    POJ NOI MATH-7829 神奇序列求和
    POJ NOI MATH-7826 分苹果
    UVALive5661 UVA668 ZOJ2037 Parliament
    POJ1032 Parliament
  • 原文地址:https://www.cnblogs.com/asd123www/p/9892716.html
Copyright © 2011-2022 走看看