2023牛客国庆集训派对day7/牛客2020年暑期多校day7

Preface

又被打爆了,计数题做不来一点,被狠狠地拉开差距

而且这场前面写题跟吃了shi一样写什么什么挂,看个J题搞了快半小时才看懂题意,建议是速速\remake

随后喜提同题数罚时倒一,虽然最后20min突然想到了I题可以用prufer序列转化,但还是差了一点没写出来

由于今天出去打印东西了所以没太多时间补题,G题就先不管了以后再说(主要这题我比赛时候没咋想屁都不会)


A. Social Distancing

这题我们队成功从开场写到4h+的时候才过,从爆搜到各个version的DP都试了个遍,这里就直接讲最后写的复杂度较好的DP做法好了

首先仔细观察式子,我们发现原式等价于\(n\times \sum_{i=1}^n (x_i^2+y_i^2)-(\sum_{i=1}^n x_i)^2-(\sum_{i=1}^n y_i)^2\)

那么不妨考虑一个DP方程,设\(f_{i,X,Y}\)表示已经放了\(i\)个点,这些点的\(\sum x=X,\sum y=Y\)时,\(\sum x^2+y^2\)的最大值

这个DP的状态数是\(n\times (nr)^2\)的,转移也是\(r^2\),然后我本机跑了下跑的有点慢,何况还有多组数据

但后面发现题目要求的其实只有\(8\times 30=240\)种情况,那么可以直接本机打表输出即可

PS:由于徐神特判\(n=2\)的情况返回了\(r^2\)而不是\(4r^2\)所以下面代码会有针对这一种情况特判,主题算法就在solve(n,r)

打表代码

#include <bits/stdc++.h>

#define RI register int
#define CI const int&

using namespace std;

typedef pair <int,int> pi;
//vector <array <int,3>> f[10];

const int S=300;

int f[10][605][605];

int solve(int n, int r) {
	if(n == 1) return 0;
	if(n == 2) return r * r;
	RI i,j,k; vector <pi> pnt;
	for (i=-r;i<=r;++i) for (j=-r;j<=r;++j)
	if (i*i+j*j<=r*r) pnt.push_back(pi(i,j));
	/*for (i=0;i<10;++i) f[i].clear();
	f[0].push_back({0,0,0});
	for (i=0;i<n;++i)
	{
		for (auto [x,y]:pnt) for (auto [pw,sx,sy]:f[i])
		f[i+1].push_back({pw+x*x+y*y,sx+x,sy+y});
		sort(f[i+1].begin(),f[i+1].end());
		f[i+1].erase(unique(f[i+1].begin(),f[i+1].end()),f[i+1].end());
	}
	int ans=0; for (auto [pw,sx,sy]:f[n]) ans=max(ans,n*pw-sx-sy);
	return ans;*/
	for (i=0;i<=n;++i) for (j=-r*n;j<=r*n;++j)
	for (k=-r*n;k<=r*n;++k) f[i][j+S][k+S]=0;
	for (i=0;i<n;++i) for (j=-r*i;j<=r*i;++j)
	for (k=-r*i;k<=r*i;++k) for (auto [x,y]:pnt)
	f[i+1][j+x+S][k+y+S]=max(f[i+1][j+x+S][k+y+S],f[i][j+S][k+S]+x*x+y*y);
	int ans=0; for (j=-r*n;j<=r*n;++j)
	for (k=-r*n;k<=r*n;++k) ans=max(ans,n*f[n][j+S][k+S]-j*j-k*k);
	return ans;
}

int main(int argc, char** argv) noexcept {
	freopen("A.cpp", "w", stdout);
	printf(
		"#include <bits/stdc++.h>\n\n"
		"int ans[9][31] = {\n"
	);
	for(int n = 0; n <= 8; ++n) {
		printf("    { %d", n);
		for(int r = 1; r <= 30; ++r) {
			fprintf(stderr, "Calculating solve(n = %d, r = %d) = ", n, r);
			int s = solve(n, r);
			fprintf(stderr, "%d\n", s);
			printf(", %6d", s);
		}
		printf(" },\n");
	}
	printf(
		"};\n"
		"\n"
		"int main() {\n"
		"    std::ios::sync_with_stdio(false);\n"
		"    int T, a, b; std::cin >> T;\n"
		"    while(T--) {\n" 
		"         std::cin >> a >> b;\n"
		"         std::cout << ans[a][b] << char(10);\n"
		"    }\n"
		"    return 0;\n"
		"}\n"
	);
	return 0;
}

提交的代码

#include <bits/stdc++.h>

int ans[9][31] = {
    { 0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0 },
    { 1,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0 },
    { 2,      1,      4,      9,     16,     25,     36,     49,     64,     81,    100,    121,    144,    169,    196,    225,    256,    289,    324,    361,    400,    441,    484,    529,    576,    625,    676,    729,    784,    841,    900 },
    { 3,      8,     32,     76,    130,    224,    312,    416,    554,    722,    896,   1064,   1248,   1512,   1746,   2016,   2264,   2600,   2888,   3218,   3584,   3912,   4344,   4712,   5138,   5612,   6062,   6536,   6984,   7520,   8084 },
    { 4,     16,     64,    144,    256,    400,    576,    784,   1024,   1296,   1600,   1936,   2304,   2704,   3136,   3600,   4096,   4624,   5184,   5776,   6400,   7056,   7744,   8464,   9216,  10000,  10816,  11664,  12544,  13456,  14400 },
    { 5,     24,     96,    218,    384,    624,    880,   1188,   1572,   2014,   2496,   2984,   3520,   4224,   4870,   5616,   6336,   7224,   8056,   9008,   9984,  10942,  12080,  13144,  14326,  15624,  16896,  18184,  19488,  20968,  22480 },
    { 6,     36,    144,    324,    576,    900,   1296,   1764,   2304,   2916,   3600,   4356,   5184,   6084,   7056,   8100,   9216,  10404,  11664,  12996,  14400,  15876,  17424,  19044,  20736,  22500,  24336,  26244,  28224,  30276,  32400 },
    { 7,     48,    192,    432,    768,   1224,   1740,   2356,   3102,   3954,   4896,   5872,   6960,   8280,   9564,  11016,  12456,  14160,  15816,  17666,  19584,  21500,  23688,  25808,  28122,  30624,  33120,  35664,  38266,  41200,  44076 },
    { 8,     64,    256,    576,   1024,   1600,   2304,   3136,   4096,   5184,   6400,   7744,   9216,  10816,  12544,  14400,  16384,  18496,  20736,  23104,  25600,  28224,  30976,  33856,  36864,  40000,  43264,  46656,  50176,  53824,  57600 },
};

int main() {
    std::ios::sync_with_stdio(false);
    int T, a, b; std::cin >> T;
    while(T--) {
         std::cin >> a >> b;
         std::cout << (a==2?4:1)*ans[a][b] << char(10);
    }
    return 0;
}


B. Mask Allocation

这题祁神手玩后发现其实就是个类似于gcd的过程,然后上去写了一发还感觉被自测的数据卡了,我直接帮他一波提交发现过了就很乐

#include<bits/stdc++.h>
using namespace std;

int t;
vector<int> ans;
void gcd(int a, int b){
	if (0==b) return;
	for (int i=0; i<a-a%b; ++i) ans.push_back(b);
	gcd(b, a%b);	
}
int main(){
	scanf("%d", &t);
	while (t--){
		ans.clear();
		int a, b;	
		scanf("%d%d", &a, &b);
		if (a<b) swap(a, b);
		gcd(a, b);
		printf("%d\n", ans.size());
		for (int x : ans) printf("%d ", x); puts("");
	}
	return 0;	
}

C. A National Pandemic

徐神刚开始搞了个听起来很神的根号分治+换根DP重构的做法,但我转念一想发现不对其实有更好写的大力树剖做法,结果爬上去写了一波WA了后发现是线段树写错了,只能说今天的状态属实有点抽象

首先考虑第一个操作怎么处理,不难发现一次操作后\(F(y)+=w-dist(x,y)=w-dep_x-dep_y+2\times dep_{\operatorname{LCA}(x,y)}\)

我们不妨用全局变量\(sum\)记录\(w-dep_x\)的值,用\(cnt\)记录操作\(1\)的次数,这样就可以很容易地处理式子的前面部分了

而后面的那个式子的做法也很经典,不妨每次修改时将\(x\)到根路径上所有点的权值加上\(1\),然后查询时求\(y\)到根路径上所有点的点权和,最后乘\(2\)即可

总复杂度\(O(n\log^2 n)\)

#include<cstdio>
#include<iostream>
#include<vector>
#define int long long
#define RI int
#define CI const int&
using namespace std;
const int N=50005;
int t,n,m,opt,x,y,sum,cnt,fix[N],dep[N]; vector <int> v[N];
class Segment_Tree
{
	private:
		int sum[N<<2],tag[N<<2],len[N<<2];
		inline void apply(CI now,CI mv)
		{
			sum[now]+=len[now]*mv; tag[now]+=mv;
		}
		inline void pushup(CI now)
		{
			sum[now]=sum[now<<1]+sum[now<<1|1];
		}
		inline void pushdown(CI now)
		{
			if (tag[now]) apply(now<<1,tag[now]),apply(now<<1|1,tag[now]),tag[now]=0;
		}
	public:
		#define TN CI now=1,CI l=1,CI r=n
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void build(TN)
		{
			sum[now]=tag[now]=0; len[now]=r-l+1;
			if (l==r) return; int mid=l+r>>1; build(LS); build(RS);
		}
		inline void modify(CI beg,CI end,CI mv,TN)
		{
			if (beg<=l&&r<=end) return apply(now,mv); int mid=l+r>>1; pushdown(now);
			if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS); pushup(now);
		}
		inline int query(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return sum[now]; int mid=l+r>>1,ret=0; pushdown(now);
			if (beg<=mid) ret+=query(beg,end,LS); if (end>mid) ret+=query(beg,end,RS); return ret;
		}
		#undef TN
		#undef LS
		#undef RS
}SEG;
namespace Tree_Divide
{
	int idx,top[N],sz[N],dfn[N],anc[N],son[N];
	inline void DFS1(CI now=1,CI fa=0)
	{
		sz[now]=1; son[now]=0; dep[now]=dep[fa]+1; anc[now]=fa;
		for (auto to:v[now]) if (to!=fa)
		{
			DFS1(to,now); sz[now]+=sz[to];
			if (sz[to]>sz[son[now]]) son[now]=to;
		}
	}
	inline void DFS2(CI now=1,CI tf=1)
	{
		dfn[now]=++idx; top[now]=tf;
		if (son[now]) DFS2(son[now],tf);
		for (auto to:v[now]) if (to!=anc[now]&&to!=son[now]) DFS2(to,to);
	}
	inline void modify(int x,int y,CI z)
	{
		while (top[x]!=top[y])
		{
			if (dep[top[x]]<dep[top[y]]) swap(x,y);
			SEG.modify(dfn[top[x]],dfn[x],z); x=anc[top[x]];
		}
		if (dep[x]<dep[y]) swap(x,y);
		SEG.modify(dfn[y],dfn[x],z);
	}
	inline int query(int x,int y,int ret=0)
	{
		while (top[x]!=top[y])
		{
			if (dep[top[x]]<dep[top[y]]) swap(x,y);
			ret+=SEG.query(dfn[top[x]],dfn[x]); x=anc[top[x]];
		}
		if (dep[x]<dep[y]) swap(x,y);
		return ret+SEG.query(dfn[y],dfn[x]);
	}
};
using namespace Tree_Divide;
inline int calc(CI x)
{
	return sum-cnt*dep[x]+2LL*query(1,x);
}
signed main()
{
	//freopen("C.in","r",stdin); freopen("C.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; for (scanf("%lld%lld",&n,&m),i=1;i<=n;++i) fix[i]=0,v[i].clear();
		for (i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
		for (sum=cnt=idx=0,DFS1(),DFS2(),SEG.build(),i=1;i<=m;++i)
		{
			scanf("%lld%lld",&opt,&x);
			if (opt==1) scanf("%lld",&y),sum+=y-dep[x],++cnt,modify(1,x,1);
			else if (opt==2)
			{
				int tmp=calc(x)+fix[x]; if (tmp>0) fix[x]-=tmp;
			} else printf("%lld\n",calc(x)+fix[x]);
		}
	}
	return 0;
}

D. Fake News

诈骗题,徐神开场写个Python然后T了,后面一打表发现只有\(n=1/24\)合法,其它均不合法

结论的具体证明可以看这里,只能说出这种题就很搞人心态

PS:这题关流同步的cout输出会T,要换puts才能过,就尼玛抽象

#include <bits/stdc++.h>

int main() {
    //std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int T; std::cin >> T;
    while(T--) {
        int64_t n; std::cin >> n;
        puts((n == 1 || n == 24 ?
            "Fake news!" :
            "Nobody knows it better than me!"));
    }
    return 0;
}

E. NeoMole Synthesis

怎么又是像化学题的东西,弃了弃了


F. Tokens on the Tree

这是啥东西,据说是动态淀粉质?


G. Topo Counting

祁神比赛的时候思路频出,但后面一看题解怎么不是组合的做法呢岂可修,先坑了以后再说


H. Dividing

傻逼闪总因为不特判\(n=1\)把自己心态写炸了红温了可真是个小丑

这题手玩以下很容易发现当\(k\)固定时,合法的\(n\)满足\(n\%k=0/1\),然后写出式子也很简单:

\[n+\sum_{i=2}^k (\lfloor\frac{n}{i}\rfloor+\lfloor\frac{n-1}{i}\rfloor+1) \]

直接除法分块求解即可,注意最后\(k>n\)的部分要记得加上,同时要特判\(n=1\)的情况

#include<cstdio>
#include<iostream>
#define int long long
#define RI int
#define CI const int&
using namespace std;
const int mod=1e9+7;
int n,k,ans,lim;
signed main()
{
	//freopen("H.in","r",stdin); freopen("H.out","w",stdout);
	RI l,r; scanf("%lld%lld",&n,&k); ans=n%mod;
	if (n==1) return printf("%lld",k%mod),0;
	for (l=2,lim=min(n,k);l<=lim;l=r+1) r=min(lim,n/(n/l)),(ans+=(r-l+1)%mod*(n/l)%mod)%=mod;
	for (--n,l=2,lim=min(n,k);l<=lim;l=r+1) r=min(lim,n/(n/l)),(ans+=(r-l+1)%mod*(n/l+1)%mod)%=mod;
	if (k>n) (ans+=k-n)%=mod; return printf("%lld",ans),0;
}

I. Valuable Forests

唉摸鱼到最后才想起来树的计数有prufer序列这个玩意,结果最后时间不够了没写出来

考虑一个点\(x\)的度数转化到prufer序列上就是它的出现次数加一,那么我们可以这样统计\(g_n\)表示\(n\)个点的无根树的权值和:

\[g_n=n\times \sum_{i=0}^{n-2}C_{n-1}^i\times (n-1)^{n-i-2}\times (i+1)^2 \]

其中对于\([1,n]\)中的每个数,我们枚举它在长为\(n-2\)的prufer序列中的出现次数\(j\),然后统计答案

接下来考虑怎么处理森林的情况,比赛的时候就是想用纯组合式子来推,后面发现答案总是差一点,看来还是得DP

不妨设\(f_n\)表示\(n\)个点构成的森林的个数,不妨设\(T_n\)表示\(n\)个点构成的无根树的数量,这个就是\(n^{n-2}\),则有:

\[f_n=\sum_{i=0}^{n-1} C_{n-1}^i\times f_{n-i-1}\times T_{i+1} \]

转移就是枚举\(n\)号点所在的树的大小\(i+1\),然后推一下就很显然了

最后考虑设\(Ans(n)\)表示\(n\)个点构成的森林对应的权值和,则转移为:

\[Ans_n=\sum_{i=0}^{n-1}C_{n-1}^i\times (Ans_{n-i-1}\times T_{i+1}+f_{n-i-1}\times g_{i+1}) \]

这个式子的意义同上,加入\(n\)号点时枚举形成的树的大小\(i+1\),考虑它的贡献会计入\(f_{n-i-1}\)次,而已经确定的部分则会乘上后面定下的方案数

最后注意由于模数可能\(\le 5000\)因此组合数要用递推法来求解,用阶乘可能会存在出现\(0\)的情况,总复杂度\(O(n^2)\)

#include<cstdio>
#include<iostream>
#define RI int
#define CI const int&
using namespace std;
const int N=5005;
int t,n,mod,pw[N][N],C[N][N],fact[N],ifac[N],f[N],g[N],ans[N];
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
	RI i,j; for (C[0][0]=i=1;i<=n;++i)
	for (C[i][0]=j=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	for (i=1;i<=n;++i) for (pw[i][0]=j=1;j<=n;++j) pw[i][j]=1LL*pw[i][j-1]*i%mod;
}
int main()
{
	//freopen("I.in","r",stdin); freopen("I.out","w",stdout);
	RI i,j; scanf("%d%d",&t,&mod); init(5000);
	for (i=2;i<=5000;++i) for (j=0;j<=i-2;++j)
	inc(g[i],1LL*C[i-2][j]*pw[i-1][i-2-j]%mod*(j+1)%mod*(j+1)%mod*i%mod);
	for (f[0]=f[1]=1,i=2;i<=5000;++i) for (j=0;j<=i-1;++j)
	inc(f[i],1LL*C[i-1][j]*(j>1?pw[j+1][j-1]:1)%mod*f[i-j-1]%mod);
	for (i=2;i<=5000;++i) for (j=0;j<=i-1;++j)
	inc(ans[i],1LL*C[i-1][j]*(1LL*(j>1?pw[j+1][j-1]:1)*ans[i-j-1]%mod+1LL*g[j+1]*f[i-j-1]%mod)%mod);
	while (t--) scanf("%d",&n),printf("%d\n",ans[n]);
	return 0;
}

J. Pointer Analysis

纯模拟题,难点在于读懂题意,然后比赛的时候写完发现没有考虑操作间的顺序遂下机反思

后面仔细一想其实只要暴力执行\(26+26=52\)次就一定可以涵盖所有情况了,然后又赶紧跑上去改了下就过了

#include<cstdio>
#include<iostream>
#include<string>
#define RI int
#define CI const int&
using namespace std;
string s[205]; int n,g[26][26],f[26][26][26];
inline void calc(void)
{
	RI i,j,k; for (k=1;k<=n;++k)
	{
		if (s[k][1]=='.') // 3
		{
			for (i=0;i<26;++i) if (g[s[k][0]-'A'][i])
			for (j=0;j<26;++j) if (g[s[k][6]-'A'][j]) f[i][s[k][2]-'a'][j]=1;
		} else if (s[k][5]=='.') // 4
		{
			for (i=0;i<26;++i) if (g[s[k][4]-'A'][i])
			for (j=0;j<26;++j) if (f[i][s[k][6]-'a'][j]) g[s[k][0]-'A'][j]=1;
		} else if (s[k][4]>='a'&&s[k][4]<='z') // 1
		{
			g[s[k][0]-'A'][s[k][4]-'a']=1;
		} else //2
		{
			for (i=0;i<26;++i) if (g[s[k][4]-'A'][i]) g[s[k][0]-'A'][i]=1;
		}
	}
}
int main()
{
	//freopen("J.in","r",stdin); freopen("J.out","w",stdout);
	RI i,j; for (scanf("%d\n",&n),i=1;i<=n;++i) getline(cin,s[i]);
	for (i=0;i<52;++i) calc();
	for (i=0;i<26;++i)
	{
		putchar('A'+i); printf(": ");
		for (j=0;j<26;++j) if (g[i][j]) putchar('a'+j);
		putchar('\n');
	}
	return 0;
}

Postscript

这就是多校题的魅力吗,给他们展示一下中国人最喜欢的计数和数据结构,然而我都做不来一点

posted @ 2023-10-05 21:39  空気力学の詩  阅读(49)  评论(0编辑  收藏  举报