2020牛客暑期多校训练营(第十场)

A Permutation

给定质数 p p p.求一个 p − 1 p-1 p1的排列 a a a.
满足 ∀ i ∈ [ 2 , p − 1 ] , a [ i ] = a [ i − 1 ] ∗ 2 % p   o r   a [ i − 1 ] ∗ 3 % p \forall i\in [2,p-1],a[i]=a[i-1]*2\%p ~or ~ a[i-1]*3\% p i[2,p1],a[i]=a[i1]2%p or a[i1]3%p.

每个数向两倍连边,那么就一定形成若干个大小相同的环.
一个环都*3即连向下一个环,所以做法就是 ∗ 2 *2 2不行就 ∗ 3 *3 3.


int T,n,p,a[N];
bool v[N];

int main() {
    qr(T); while(T--) {
        qr(p); n=p-1;
        for(int i=1;i<=n;i++) v[i]=0;
        v[a[1]=1]=1;
        bool flag=1;
        for(int i=2;i<=n;i++) {
            int x=a[i-1];
            if(!v[x*2%p]) v[a[i]=x*2%p]=1;
            else if(!v[x*3%p]) v[a[i]=x*3%p]=1;
            else {flag=0;break;}
        }
        if(!flag) puts("-1");
        else {
            for(int i=1;i<=n;i++) pr1(a[i]);
            puts("");
        }
    }
    return 0;
}

B KMP Algorithm

参考blog:https://blog.csdn.net/weixin_43965698/article/details/107945812

字符串匹配不难想到卷积形式.
而这样做是 O ( 26 n log ⁡ n ) O(26n\log n) O(26nlogn)的.
我们考虑利用性质.
f [ i ] = ( s [ i ] = = c ) , g [ i ] = ( t [ i ] = = c ) f[i]=(s[i]==c),g[i]=(t[i]==c) f[i]=(s[i]==c),g[i]=(t[i]==c).
D F [ i ] = f [ i ] − f [ i + d ] , D G [ i ] = g [ i ] − g [ i + d ] DF[i]=f[i]-f[i+d],DG[i]=g[i]-g[i+d] DF[i]=f[i]f[i+d],DG[i]=g[i]g[i+d].
可以 D F [ i ] , D G [ i ] DF[i],DG[i] DF[i],DG[i]只有 O ( d ) O(d) O(d)个非0 位置,故暴力卷积即可.
( D F ∗ D G ) [ i ] = ∑ x + y = i D F [ x ] ⋅ D G [ y ] = ∑ x + y = i ( f [ x ] − f [ x + d ] ) ∗ ( g [ y ] − g [ y + d ] ) = ∑ x + y = i f [ x ] g [ y ] − 2 f [ x + d ] g [ y ] + f [ x + d ] g [ y + d ] → ( f ∗ g ) [ i ] = ( D F ∗ D G ) [ i ] + 2 ( f ∗ g ) [ i + d ] − ( f ∗ g ) [ i + 2 d ] (DF*DG)[i]=\sum_{x+y=i} DF[x]\cdot DG[y]=\sum_{x+y=i}(f[x]-f[x+d])*(g[y]-g[y+d])=\sum_{x+y=i} f[x]g[y]-2f[x+d]g[y]+f[x+d]g[y+d]\rightarrow (f*g)[i]=(DF*DG)[i]+2(f*g)[i+d]-(f*g)[i+2d] (DFDG)[i]=x+y=iDF[x]DG[y]=x+y=i(f[x]f[x+d])(g[y]g[y+d])=x+y=if[x]g[y]2f[x+d]g[y]+f[x+d]g[y+d](fg)[i]=(DFDG)[i]+2(fg)[i+d](fg)[i+2d].

int d,n,m,cnt[N],same[N];
char s[N],t[N];
vector<pii> f[28],g[28];

int main() {
    scanf("%d %s %s",&d,s+1,t+1);
    n=strlen(s+1); m=strlen(t+1); reverse(t+1,t+m+1);
    for(int i=1;i<=n;i++) s[i] -= 'a'-1;
    for(int i=1;i<=m;i++) t[i] -= 'a'-1;
    for(int i=1;i<=d;i++) f[s[i]].pb(mk(i-d,-1)),g[t[i]].pb(mk(i-d,-1));
    for(int i=1;i<=n;i++) if(s[i] != s[i+d]) {
        f[s[i]].pb(mk(i,1));
        if(i+d<=n) f[s[i+d]].pb(mk(i,-1));
    }
    for(int i=1;i<=m;i++) if(t[i] != t[i+d]) {
        g[t[i]].pb(mk(i,1));
        if(i+d<=m) g[t[i+d]].pb(mk(i,-1));
    }
    for(int c=1;c<=26;c++)
        for(auto i:f[c])
            for(auto j:g[c])
                if(i.fi+j.fi>m) cnt[i.fi+j.fi-m] += i.se*j.se;
    for(int i=n;i;i--) same[i]=cnt[i]+2*same[i+d]-same[i+2*d];
    for(int i=1;i<=n-m+1;i++) pr2(m-same[i]);
    return 0;
}

C Decrement on the Tree

给定一棵树,边带权.
一次操作为把一个路径上的所有边的权-1.
问最少操作数使得所有边的边权为0.
同时要能支持边权修改.
n , q ≤ 1 e 5 n,q\le 1e5 n,q1e5.

路径数等价于数端点数/2.
我们从每个点考虑.
设一个点 x x x连出的最大边为 m x , s [ x ] mx,s[x] mx,s[x]表示出边权值总和.,若 2 m x > s [ x ] 2mx> s[x] 2mx>s[x]
那么 2 m x − s [ x ] 2mx-s[x] 2mxs[x]条边的端点为 x x x.
其他情况边权一定能够两两配对,剩余 s [ x ] % 2 s[x]\%2 s[x]%2条边的端点为 x x x.
m u l t i s e t multiset multiset维护出边最大值即可.
总复杂度为 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn).

int n,m;
ll s[N],ans;
multiset<int,greater<int> > w[N];

struct E{int x,y,z;} e[N];

int f(int x) {
    int mx=*w[x].begin();
    if(mx*2>s[x]) return mx*2-s[x];
    return s[x]&1;
}

void add(int x,int y,int z) {
    s[x] += z; s[y] += z;
    w[x].insert(z); 
    w[y].insert(z);
    ans += f(x)+f(y);
}

void del(int x,int y,int z) {
    ans -= f(x)+f(y);
    w[x].erase(w[x].find(z));
    w[y].erase(w[y].find(z));
    s[x] -= z; s[y] -= z;
}

int main() {
    qr(n); qr(m);
    for(int i=1,x,y,z;i<n;i++) {
        qr(x); qr(y); qr(z);
        add(x,y,z);
        e[i]=(E){x,y,z};
    }
    ans=0;
    for(int i=1;i<=n;i++) ans += f(i);
    pr2(ans/2);
    while(m--) {
        int op,x,y,z; qr(op); qr(z);
        x=e[op].x; y=e[op].y;
        del(x,y,e[op].z);
        add(x,y,e[op].z=z);
        pr2(ans/2);
    }
    return 0;
}

D Hearthstone Battlegrounds

炉石小游戏.
有4种特效角色(A类):

  1. 剧毒+圣盾+亡语
  2. 剧毒+圣盾
  3. 剧毒+亡语
  4. 剧毒.
    剧毒角色的属性为 1 / 1 0 9 1/10^9 1/109. x / y x/y x/y表示攻击为 x x x,生命为 y y y.
    亡语的效果为召唤一只 1 / 1 1/1 1/1.(B类)
    现在已知双方的角色数量,问先手是否用获胜概率.

我们要判定先手是否能赢.
实际上就是先手采取最优策略,后手采取最坏策略.
对于小兵 ( 1 / 1 ) (1/1) (1/1),先手要用肯定用来破盾.
而对于后手,肯定和A撞最傻.

我们的策略是:

  1. 尽量用亡语.
  2. 尽量先撞无盾的.

int a,b,c,d;
int e,f,g,h;
int s1,s2;

/*
1. 剧毒+圣盾+亡语
2. 剧毒+圣盾
3. 剧毒+亡语
4. 剧毒.
*/

bool fight() {
    //破盾
    if(s1 && e) {s1--; e--; g++; return 1;}
    if(s1 && f) {s1--; f--; h++; return 1;}
    //送命
    if(s2 && (a+b+c+d)>0) {s2=0; return 1;}
    //动用召唤去撞无盾
    if(c && g) {c--; s1++; g--; s2++; return 1;}
    if(a && g) {a--; c++; g--; s2++; return 1;}
    if(c && h) {c--; s1++; h--; return 1;}
    if(a && h) {a--; c++; h--; return 1;}
    //对方有盾.先用召唤的去换,实在不行再用无召唤的搏.
    if(c && e) {c--; s1++; e--; g++; return 1;}
    if(c && f) {c--; s1++; f--; h++; return 1;}
    if(a && e) {a--; c++; e--; g++; return 1;}
    if(a && f) {a--; c++; f--; h++; return 1;}
    if(b && e) {b--; d++; e--; g++; return 1;}
    if(b && f) {b--; d++; f--; h++; return 1;}
    if(d && e) {d--; e--; g++; return 1;}
    if(d && f) {d--; f--; h++; return 1;}
    //其他撞无盾
    if(b && g) {b--; d++; g--; s2++; return 1;}
    if(d && g) {d--; g--; s2++; return 1;}
    if(b && h) {b--; d++; h--; return 1;}
    if(d && h) {d--; h--; return 1;}
    return 0;
}

int main() {
    int T; qr(T); while(T--) {
        qr(a); qr(b); qr(c); qr(d);
        qr(e); qr(f); qr(g); qr(h);
        s1=s2=0;
        while(fight());
        if(a+b+c+d>0) puts("Yes");
        else if(e+f+g+h>0) puts("No");
        else if(s1<=s2) puts("No");
        else puts("Yes");
    }
}

E Game

推箱子:给你一个 n n n列的仓库.每列堆放着一些 1 ∗ 1 ∗ 1 1*1*1 111的箱子.
你可以把箱子右移,使得最高的箱子最低.

前缀最大平均值

int n,T;
ll sum,x,ans;

int main() {
	qr(T); while(T--) {
		qr(n); ans=sum=0;
		for(int i=1;i<=n;i++) {
			qr(x); sum += x;
			ans=max(ans,(sum+i-1)/i); 
		} 
		pr2(ans);
	}
}

I Tournament

n n n支队伍,要求两两比赛.(总共 n ∗ ( n − 1 ) / 2 n*(n-1)/2 n(n1)/2场)
一支队伍在首次比赛时入场,最后一次比赛后离开.
输出比赛方案使得所有队伍的在场时间和最小.

参考blog :https://blog.csdn.net/qq_45458915/article/details/107924613
看到样例,容易想到升序莽过去…
然而,这种方法劣在最小化1号队伍,其他队伍都要等很久.

我们可以考虑在升序的情况下优化.
如果我们前移 ( i , j ) ( i < j ) (i,j)(i<j) (i,j)(i<j) (运到 ( 1 , j + 1 ) (1,j+1) (1,j+1)之前) 则变化为 i − 1 − ( n − j ) i-1-(n-j) i1(nj).
即前面的 i i i的结束时间+1, n − j n-j nj个的开始时间-1.
由此我们可以先把所有满足 i − 1 − ( n − j ) < 0 i-1-(n-j)<0 i1(nj)<0 ( i , j ) (i,j) (i,j)前移.
这一部分的话,有上述前移的位置可以知道要先按 j j j升序,再按 i i i升序.

剩下部分我们升序输出即可.(既然前移已经不能优化,那么我们肯定要让先加入的尽量快结束.)

qr(T); while(T--) {
        qr(n);
        for(int j=2;j<=n;j++)
            for(int i=1;i<min(j,n-j+1);i++)
                pr1(i),pr2(j);
        for(int i=1;i<=n;i++) 
            for(int j=i+1;j<=n;j++)
                if(i>=min(j,n-j+1))
                    pr1(i),pr2(j);
    }

J Identical Trees

给定两颗有根树,你需要修改树上标号使得两棵树一样.
求最小的编号修改次数. n ≤ 500 n\le 500 n500.

树形DP.
两两子树进行匹配.
然后用二分图最大匹配合并答案.

复杂度上界为 O ( n 4 ) O(n^4) O(n4),但是并不好卡,实际复杂度应该是 O ( n 3 ) O(n^3) O(n3)级别的.

const int N=510;

int n;
struct Graph {
    int rt;
    struct edge{int y,next; } a[N]; int len,last[N],deg[N];
    void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len; }
    ull h[N];
    void dfs(int x) {
        vector<ull> t;
        for(int k=last[x],y;k;k=a[k].next) dfs(y=a[k].y),t.pb(h[y]);
        sort(all(t)); h[x]=1; for(auto i:t) h[x]=h[x]*2333+i;
    }
    void init() {
        for(int i=1,f;i<=n;i++) {
            qr(f);
            if(!f) rt=i;
            else ins(f,i),deg[f]++;
        }
        dfs(rt);
    }
} u,v;

namespace Flow {
    const int M=N*N;
    struct edge{int y,next,d,c;} a[M]; int len=1,last[N*2];
    int q[N*2],d[N*2],pre[N*2],st,ed,T;
    void ins(int x,int y,int c,int d) {a[++len]=(edge){y,last[x],d,c}; last[x]=len; }
    void add(int x,int y,int c,int d) {ins(x,y,c,d); ins(y,x,0,-d); }
    void clear(int x) {
    	T=x;
        int *p=last+1;  while(*p) *p++=0;
        len=1; st=1; ed=2; 
    }
    int EK() {
    	if(!T) return 0;
        int ans=0,cnt=T;
        while(1) {
            int l=1,r=2; q[l]=st;
            memset(d+1,63,sizeof(int)*(2*T+2)); d[st]=pre[ed]=0;//每次找最小边增广
            while(l != r) {
                int x=q[l++];  if(l==N*2) l=1; 
                for(int k=last[x],y;k;k=a[k].next) 
                    if(d[y=a[k].y]>d[x]+a[k].d&&a[k].c) {
                        d[y]=d[x]+a[k].d;
                        pre[y]=k^1;
                        q[r++]=y;
                        if(r==N*2) r=1;
                    }
            }
            ans += d[ed];
            if(!--cnt) return ans;//流必须流满 
            if(!pre[ed]) return M;//否则返回INF  
            int x=ed;
            while(x^st) {
                int k=pre[x];
                a[k].c++;
                a[k^1].c--;
                x=a[k].y;
            }
        }
    }
}

int f[N][N];

int solve(int x,int y) {
    if(u.h[x] ^ v.h[y]) return N;//hash预判. 
    for(int A=u.last[x];A;A=u.a[A].next) 
        for(int B=v.last[y];B;B=v.a[B].next)
            f[u.a[A].y][v.a[B].y]=solve(u.a[A].y,v.a[B].y);
    int tot=u.deg[x],dx=2,dy;
    if(tot ^ v.deg[y]) return N; //双重保险.(保证判定树同构的正确性) 
    Flow::clear(tot);
    for(int A=u.last[x];A;A=u.a[A].next) {
        ++dx; Flow::add(1,dx,1,0); dy=tot+2;
        for(int B=v.last[y];B;B=v.a[B].next) {
            dy++; int val=f[u.a[A].y][v.a[B].y];
            if(val<=n) 
				Flow::add(dx,dy,1,val);
            if(dx == 3)Flow::add(dy,2,1,0);
        }
    }
    return Flow::EK()+(x!=y);
}

int main() {
    qr(n);
    u.init(); v.init();
    pr2(solve(u.rt,v.rt));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值