11.2

发布时间 2023-11-03 16:11:06作者: FLOR丨Z

赛后关网罪大恶极

T1 上海

直接算数基本定理

点击查看代码
#include<bits/stdc++.h>
typedef int count ;
typedef long long value;
inline value read(){
    count f(1);
    value x(0);
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
    return f*x;
}
void write(value x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
#define debug(x)  std::cout<<"###"<<x<<std::endl;


#define maxn 1000010

value k;

value p[maxn],c[maxn],cnt;

bool isnt[maxn];
value prime[maxn],num;

inline value kuai(value base,count x){
    value val=1;
    while(x){
        if(x&1) val=val*base;
        base=base*base;
        x>>=1;
    }
    return val;
}

int main(){
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);

    k=read();
    for(count i=2;i<=sqrt(k);i++){
        if(!isnt[i]){
            prime[++num]=i;
        }
        for(count j=1;j<=num && i*prime[j]<=sqrt(k);j++){
            isnt[i*prime[j]]=1;
            if(i%prime[j]==0){
                break;
            }
        }
    }
    
    for(count i=1;i<=num;i++){
        count now=prime[i];
        // debug("OK")
        if(k%now==0){
            // debug(now)
            p[++cnt]=now;
            while(k%now==0){
                c[cnt]++;
                k/=now;
            }
        }
    }

    if(k!=1){
        p[++cnt]=k;
        c[cnt]=1;
        k=1;
    }
    
    bool flag=0;
    for(count i=1;i<=cnt;i++){
        if(c[i]>1){
            flag=1;
            break;
        }
    }
    if(!flag ){
        write(-1);
        return 0;
    }

    value ans=1;
    for(count i=1;i<=cnt;i++){
        ans=(ans*kuai(p[i],(c[i]>>1)+(c[i]&1)));
    }
    write(ans);
    return 0;
}

T2 华二

赛时没想到6该怎么办。悲。

首先除了6和157之外,其他的数可以分为能被2整除的和能被3整除的。这两段相当于两个顺序确定的序列合并保持相对顺序不变 \(\dbinom{a+b}{a}\)

这些显然不能和6换,相对6位置不变,就把整个序列按6分成一段一段的。

其他的157也看作不能交换得按有顺序插入。

点击查看代码
#include<bits/stdc++.h>
typedef int count ;
typedef long long value;
inline value read(){
    count f(1);
    value x(0);
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
    return f*x;
}
void write(value x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
#define debug(x)  std::cout<<"###"<<x<<std::endl;

#define mod 998244353
#define maxn 100010
count n;
value a[maxn];

value jie[maxn<<1],ni[maxn<<1];
inline value kuai(value base,value x){
    value val=1;
    while(x){
        if(x&1) val=val*base%mod;
        base=base*base%mod;
        x>>=1;
    }
    return val;
}

inline value suan(value n,value m){
    value val=jie[n]*ni[m]%mod*ni[n-m]%mod;
    return (val? val:1);
}

int main(){
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);

    n=read();
    for(count i=1;i<=n;i++){
        a[i]=read();
    }

    jie[0]=ni[0]=1;
    for(count i=1;i<=n+n+5;i++){
        jie[i]=jie[i-1]*i%mod;
        ni[i]=ni[i-1]*kuai(i,mod-2)%mod;
        // debug(ni[i])
    }

    value ans=1;
    value all=0;
    for(count i=0;i<=n;i++){
        value cnt2=0,cnt3=0;
        while(a[i+1]!=6 && i<n){
            i++;
            if(a[i]==2 || a[i]==4 || a[i]==8) cnt2++;
            if(a[i]==3 || a[i]==9) cnt3++; 
        }
        all+=cnt2+cnt3+(a[i+1]==6);
        ans=(ans*suan(cnt2+cnt3,cnt2))%mod;
    }

    count cnt1=0,cnt5=0,cnt7=0;
    for(count i=1;i<=n;i++){
        if(a[i]==1) cnt1++;
        if(a[i]==5) cnt5++;
        if(a[i]==7) cnt7++;
    }

    ans=(ans*suan(all+cnt1,all))%mod;
    all+=cnt1;
    ans=(ans*suan(all+cnt5,all))%mod;
    all+=cnt5;
    ans=(ans*suan(all+cnt7,all))%mod;
    write(ans),putchar('\n');
    return 0;
}

T3 高爸(够吧

赛时没啥太好的思路,只会打70pts的还挂了特殊性质。枚举值域的做法。

首先设 \(x\) 为比当前值 \(val\) 大的有多少个能量值,\(sum_x\) 为它们到 \(val\) 的距离之和,\(y\)\(sum_y\) 小但是同理。\(ans=asum_y+bsum_x\)

然后我们给 \(val\) 增加 1。\(ans=a(sum_x-x)+b(sum_y+y)\)\(\Delta=-bx+ay\)。在任意的确定的两个能量值之间,这个值肯定是不变的,所以如果它是负的就得让它负的更多,所以一定要到它的端点,所以最优决策一定在某个确定的龙的能量值的位置。

好吧这是赛时就知道的,但是只是证了在龙的能量值的位置取到最优。没往后想。

所以如果我们减少到了下一个能量值,此时 \(x,y\) 发生了变化。\(\Delta=-b(x-1)+a(y+1)\),因为减少会更优我们才让它减少,我们钦定它在变化前是个负数。它变得更大了。相当于斜率变大了。

所以这个函数的图像大概就:

可以按斜率使用二分(好像要用平衡树维护吧大概)或者三分

我按值域三分没卡过去(

70pts,2000~2700ms跑完
#include<bits/stdc++.h>
typedef int count ;
typedef long long value ;
inline value read(){
    count f(1);
    value x(0);
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
    return f*x;
}
void write(value x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
#define debug(x) std::cerr<<"###"<<x<<std::endl;


#define maxn 100010

count n,m;
value a,b;
value x[maxn],zhen[maxn];

value book[maxn];

count all,rt[maxn];
struct tree{
    count l,r;
    count ls,rs;
    value sum;
    value cnt;
} shu[maxn<<5];
inline void copy(count &hao){
    shu[++all]=shu[hao];
    hao=all;
}
inline void pushup(count hao){
    shu[hao].cnt=(shu[shu[hao].ls].cnt+shu[shu[hao].rs].cnt);
    shu[hao].sum=(shu[shu[hao].ls].sum+shu[shu[hao].rs].sum);
    // debug(shu[hao].sum)
}
void insert(count &hao,count l,count r,count pos){
    copy(hao);
    shu[hao].l=l,shu[hao].r=r;
    if(l==r){
        shu[hao].cnt++;
        shu[hao].sum+=book[pos];
        return ;
    }
    count mid=(l+r)>>1;
    if(pos<=mid)  insert(shu[hao].ls,l,mid,pos);
    else insert(shu[hao].rs,mid+1,r,pos);
    pushup(hao);
}
value askda(count hao,count l,count r,count jian){
    if(!hao) return 0;
    if(l<=shu[hao].l && r>=shu[hao].r){
        // debug('$'<<shu[hao].cnt<<' '<<shu[hao].sum<<' '<<jian)
        value val=std::max(0ll,shu[hao].sum-shu[hao].cnt*jian);
        return val;
    }
    count mid=(shu[hao].l+shu[hao].r)>>1;
    value val=0;
    if(l<=mid) val+=askda(shu[hao].ls,l,r,jian);
    if(r>mid) val+=askda(shu[hao].rs,l,r,jian);
    return val;
}
value askx(count hao,count l,count r,count jian){
    if(!hao) return 0;
    if(l<=shu[hao].l && r>=shu[hao].r){
        // debug('&'<<shu[hao].cnt<<' '<<shu[hao].sum<<' '<<jian)
        return std::max(0ll,shu[hao].cnt*jian-shu[hao].sum);
    }
    count mid=(shu[hao].l+shu[hao].r)>>1;
    value val=0;
    if(l<=mid) val+=askx(shu[hao].ls,l,r,jian);
    if(r>mid) val+=askx(shu[hao].rs,l,r,jian);
    return val;
}

inline value f(value i,value ll){
    value da=b*std::max(0ll,askda(rt[i],ll+1,m,book[ll]));
    value xiao=a*std::max(0ll,askx(rt[i],1,ll-1,book[ll]));
    // debug(da<<' '<<xiao<<' '<<da+xiao)
    return da+xiao? da+xiao:1;
    // return ask(rt[i],1,m,book[ll]);
}

int main(){
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);

    n=read(),a=read(),b=read();
    for(count i=1;i<=n;i++){
        x[i]=read();
        book[i]=x[i];
        // debug(x[i])
    }
    
    std::sort(book+1,book+1+n);
    m=std::unique(book+1,book+1+n)-book-1;
    for(count i=1;i<=n;i++){
        zhen[i]=std::lower_bound(book+1,book+1+m,x[i])-book;
        // debug(zhen)
        rt[i]=rt[i-1];
        insert(rt[i],1,book[m],zhen[i]);
    }

    // debug(asksum(rt[n],1,n))

    // debug("OK")
    write(0),putchar('\n');
    count pre=1;
    for(count i=2;i<=n;i++){
        count l=pre,r=zhen[i];
        if(l>r) std::swap(l,r);
        value ans=1ll << 60;
        while(l+10<r){
            value ll=l+4*(r-l)/9;
            value rr=r-4*(r-l)/9;
            value lm=f(i,ll);
            value rm=f(i,rr);
            if(lm<rm){
                r=rr;
            }
            else{
                l=ll;
            }
        }
        for(count j=l;j<=r;j++){
            if(f(i,j)<ans){
                ans=f(i,j);
                pre=j;
            }
        }
        write(ans),putchar('\n');
    }

    return 0;
}

/*
5 3 2
5 1 4 2 3
*/

T4 金牌

赛时没时间打完力(悲)

对于询问的两个点不是互为祖孙的情况,\(ans=\sum_{i\in subtree_x} \sum_{j\in subtree_y} 2^{dis_{x,y}+dis_{i,x}+dis_{j,y}}\)

利用什么因式分解能 \(ans=(\sum 2^{dis_{i,x}})\times (\sum 2^{dis{j,y}})\times 2^{dis_{x,y}}\)

这个可以dfs一遍直接求出每棵子树的贡献之和,求 lca 算第三项。

对于一点为另一点祖先的情况,设 \(x\) 为祖先。换个根,把 \(x,y\) 中间某个点看作根,这个形状好像和前面的式子适用的情况一样。发现原本式子里面的 \(\sum 2^{dis_{i,x}}\) 就是全局经过 \(x\) 的路径的贡献减去 \(x\) 的儿子里包含 \(y\) 的子树的贡献,然后代刚才的式子就好。

有个树链剖分求祖先直接儿子的技巧,就是往上跳,如果当前在轻链上必定有个时候是当前点 \(top\) 的爹是祖先,直接返回 \(top\);如果在重链上(就是上面没跳到)就直接从祖先的位置的 \(dfn\) +1,然后找当前 \(dfn\) 对应的点。

点击查看代码
#include<bits/stdc++.h>
typedef int count ;
typedef long long value;
inline value read(){
    count f(1);
    value x(0);
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
    return f*x;
}
void write(value x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
#define debug(x)  std::cerr<<"###"<<x<<std::endl;

#define mod 998244353
#define maxn 1000010
count n,q;

count head[maxn],tot;
struct bian{
	count to,nx;
} bian[maxn<<1];
inline void add(count from,count to){
	bian[++tot].to=to;
	bian[tot].nx=head[from];
	head[from]=tot;
}

count num,in[maxn],out[maxn];
count dep[maxn],top[maxn],f[maxn],son[maxn],size[maxn];
value dp[maxn],wai[maxn];
void dfs1(count x,count fa){
	dep[x]=dep[fa]+1;
	size[x]=1;
    dp[x]=1;
	for(count i=head[x];i;i=bian[i].nx){
		count y=bian[i].to;
		if(y==fa) continue;
		f[y]=x;
		dfs1(y,x);
		size[x]+=size[y];
		if(size[y]>size[son[x]]){
			son[x]=y;
		}
        dp[x]=(dp[x]+dp[y]*2ll%mod)%mod;
	}
    out[x]=num;
}
count duiy[maxn];
void dfs2(count x,count topf){
	top[x]=topf;
	in[x]=++num;
    duiy[num]=x;
    wai[x]=dp[x];
    if(!son[x]) return ;

    value cunx=dp[x],cuny=dp[son[x]];
    dp[x]=((dp[x]-2ll*dp[son[x]]%mod)+mod)%mod;
    dp[son[x]]=(dp[son[x]]+2ll*dp[x]%mod)%mod;
	
	dfs2(son[x],topf);
	dp[x]=cunx;
    dp[son[x]]=cuny;

	for(count i=head[x];i;i=bian[i].nx){
		count y=bian[i].to;
		if(y==f[x]  || y==son[x]) continue;
        value cunx=dp[x],cuny=dp[y];
        dp[x]=(dp[x]-2ll*dp[y]%mod+mod)%mod;
        dp[y]=(dp[y]+2ll*dp[x]%mod+mod)%mod;
		dfs2(y,y);
        dp[x]=cunx;
        dp[y]=cuny;
	}
    out[x]=num;
}
inline count lca(count x,count y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) std::swap(x,y);
        x=f[top[x]];
    }
    if(dep[x]>dep[y]) std::swap(x,y);
    return x;
}
inline count xia(count x,count y){
    while(top[x]!=top[y]){
        if(f[top[x]]==y) return top[x];
        x=f[top[x]];
    }
    return duiy[in[y]+1];
}

inline value kuai(value base,count x){
    value val=1;
    while(x){
        if(x&1) val=val*base%mod;
        base=base*base%mod;
        x>>=1;
    }
    return val;
}

value base[maxn];

int main(){
    freopen("d.in","r",stdin);
    freopen("d.out","w",stdout);

    n=read();
    for(count i=1;i<=n-1;i++){
        count b=read(),c=read();
        add(b,c);
        add(c,b);
    }

    base[0]=1;
    for(count i=1;i<=n;i++){
        base[i]=base[i-1]*2ll%mod;
        // debug(base[i])
    }

    dfs1(1,0);
    dfs2(1,1);

    q=read();
    while(q--){
        count x=read(),y=read();
        count an=lca(x,y);

        // debug(an)
        if(in[x]>in[y]) std::swap(x,y);
        if(in[y]<=out[x]){
            count sec=xia(y,x);
            // debug(sec)
            count dis=dep[y]-dep[x];
            value sum=(wai[x]-2ll*dp[sec]%mod+mod)%mod;
            value ans=1;
            ans=(ans*base[dis]%mod*dp[y]%mod*sum)%mod;
            write(ans),putchar('\n');
        }else{
            value dis=dep[x]+dep[y]-(dep[an]<<1);
            value ans=base[dis]%mod;
            ans=(ans*dp[x]%mod*dp[y])%mod;
            write(ans),putchar('\n');
        }   
    }
    return 0;
}