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",n-4,n);
        else if(n%4==1) printf("2 %lld\n",n-1);
        else if(n%4==2) printf("2 %lld\n",n);
        else printf("%lld %lld\n",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}\binom{t}{i}*\binom{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}\binom{n}{i}*\binom{m}{c+i}=\sum_{i=0}^{n}\binom{n}{n-i}*\binom{m}{c+i}=\binom{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\n",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\n",ans);
}
View Code

 

posted @ 2018-11-01 21:37  asd123www  阅读(296)  评论(0编辑  收藏  举报