noip52

T1

没啥好说的,打表找规律就能过。

但考场用的时间比较多,一方面找的有些慢,另一方面担心自己正确性有问题,写了对拍也放不下。

下次类似问题应避免发生,担心正确性就写个对拍去看下一道题,过一会儿再回来看。

T2

一开始没搞明白它想干嘛,看样例解释才知道一开始有一个筹码,但还是不太会,就先弃掉了,剩1h后来,拿20pts就跑路了。

正解:

题解做法不太明白,这里是XIN的贪心做法。先解释一下样例。

对于一开始不压筹码,相当于每个都放了 \(\frac{1}{2}\) 个,这样黑手扔球后我还是1个筹码。

现在剩下 \(\{2,1\}\) ,分别压了 \(\frac{2}{3}\)\(\frac{1}{3}\) ,然后黑手控制red少了一个。

剩下\(\{1,1\}\) , 题面上是各压了 \(\frac{1}{3}\) ,但实际上,你各压 \(\frac{2}{3}\) 也是等价的,然后这时黑手扔那个都是一样的。

最后只能扔一个了,全部都压到上面。按照这种操作来,该样例最后结果仍然是 \(\frac{8}{3}\)

所以就能得到一个结论,对于不同种类的球,幕后黑手每次扔个数最多的那一种,即使球平均,压筹码按比例压一定最优。

用优先队列模拟一下这个过程即可,这样做是 \(O(n\log n)\) ,用桶+指针乱移可做到 \(O(n)\)

对于不会的题,要敢想敢做,不能轻易弃掉。

T3

考场觉得是个点分治,应该能拿35pts,然而直接ac???,后来被卡到70pts

70pts: 大力点分治。原数据能过,但XIN加强之后只有70pts了。

100pts:

斯特林展开不会。

回来填坑。

其实是原题,crash的文明世界

做法也是一样的...

设一个点 \(u\) 的答案为 \(ans_{u}\) ,那么则有:

\[\begin{aligned} ans_{u}&=\sum_{i=1}^{n}dis(i,u)^k\\ &=\sum_{i=1}^{n}\sum_{j=0}^{k}\begin{Bmatrix}k\\j\end{Bmatrix}j!\tbinom{dis(i,u)}{j}\\ &=\sum_{j=0}^{k}\begin{Bmatrix}k\\j\end{Bmatrix}\;j!\sum_{i=1}^{n}\tbinom{dis(i,u)}{j}\\ \end{aligned} \]

于是现在就要对于每个点 \(u\) ,求出其 \(\sum_{i=1}^{n}\tbinom{dis(i,u)}{j}\)

跑个树形dp+换根dp即可。

\(dp_{1\;u,i}\) 表示来自 \(u\) 子树的贡献,\(dp_{2\;u,i}\) 表示来自 \(u\) 父亲的贡献。

对于 \(dp_{1\;u,i}\) ,计算 \(u\) 之前其儿子 \(v\) 的答案肯定已经计算过,所以可以通过 \(v\)\(v\) 的子树,得到 \(u\) 的答案,即:

\[\begin{aligned} dp_{1\;u,i}&=\sum_{v\in subtree_{u}}\tbinom{dis(u,v)}{i}\\ &=\sum_{v\in son_{u}}\sum_{to\in subtree_{v}}\tbinom{dis(v,to)+1}{i}\\ \end{aligned} \]

再由组合数的递推公式 \(\tbinom{n}{m}=\tbinom{n-1}{m}+\tbinom{n-1}{m-1}\) ,可以得到:

\[\begin{aligned} dp_{1\;u,i}&=\sum_{v\in son_{u}}\sum_{to\in subtree_{v}}\tbinom{dis(v,to)}{i}+\tbinom{dis(v,to)}{i-1}\\ &=\sum_{v\in son_{u}}\left(\sum_{to\in subtree_{v}}\tbinom{dis(v,to)}{i}+\sum_{to\in subtree_{v}}\tbinom{dis(v,to)}{i-1}\right)\\ &=\sum_{v\in son_{u}}dp_{1\;v,i}+dp_{1\;v,i-1}\\ \end{aligned} \]

然后跑个换根dp,最后答案求完和,再除以2即可。

Code
#include<cstdio>
#include<cctype>
#define MAX 1000003
#define re register
#define int64_t long long
const int K = 103;
const int inv = 499122177;
const int mod = 998244353;
namespace some
{
	struct stream
	{
		template<typename type>inline stream &operator >>(type &s)
		{
			bool w=0; s=0; char ch=getchar();
			while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
			while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
			return s=w?-s:s,*this;
		}
	}cin;
	struct graph
	{
		int next;
		int to;
	}edge[MAX<<1];
	int cnt=1,head[MAX];
	auto add = [](int u,int v) -> void { edge[++cnt] = (graph){head[u],v},head[u] = cnt; };
	auto fd = [](int a,int b) -> int { return b-mod+a>=0?b-mod+a:a+b; };
	auto line = []() { printf("\n"); };
	auto begin = []() { printf("begin\n"); }; auto end = []() { printf("end\n"); };
}using namespace some;
namespace OMA
{
	int n,k,ans;
	int str[K][K],fac[K];
	int dp1[MAX][K],dp2[MAX][K]; // val from subtree , val frome father
	void dfs_up(int u,int fa)
	{
		dp1[u][0] = 1;
		for(re int i=head[u],v; i; i=edge[i].next)
		{
			v = edge[i].to;
			if(v!=fa)
			{
				dfs_up(v,u);
				dp1[u][0] = fd(dp1[u][0],dp1[v][0]);
				for(re int j=1; j<=k; j++)
				{ dp1[u][j] = fd(dp1[u][j],fd(dp1[v][j-1],dp1[v][j])); }
			}
		}
	}
	void dfs_down(int u,int fa)
	{
		for(re int i=head[u],v; i; i=edge[i].next)
		{
			v = edge[i].to;
			if(v!=fa)
			{
				dp2[v][0] = ((fd(dp2[u][0],dp1[u][0])-dp1[v][0])+mod)%mod;
				dp2[v][1] = (fd(fd(dp2[u][1],dp2[u][0]),fd(dp1[u][1],dp1[u][0]))-fd(dp1[v][1],2*dp1[v][0]%mod))%mod;
				for(re int j=2; j<=k; j++)
				{
					dp2[v][j] = ((fd(fd(dp2[u][j],dp2[u][j-1]),fd(dp1[u][j],dp1[u][j-1]))-fd(fd(dp1[v][j],2*dp1[v][j-1]%mod),dp1[v][j-2]))%mod+mod)%mod;
				}
				dfs_down(v,u);
			}
		}
	}
	auto main = []() -> signed
	{
		//freopen("node.in","r",stdin);
		cin >> n >> k;
		for(re int i=2,u,v; i<=n; i++)
		{ cin >> u >> v; add(u,v),add(v,u); }
		str[0][0] = fac[0] = 1;
		for(re int i=1; i<=k; i++)
		{
			fac[i] = 1ll*fac[i-1]*i%mod;
			for(re int j=1; j<=i; j++)
			{ str[i][j] = fd(str[i-1][j-1],1ll*j*str[i-1][j]%mod); }
		}
		dfs_up(1,0),dfs_down(1,0);
		//begin(); for(re int i=1; i<=n; i++) { for(re int j=0; j<=k; j++) { printf("dp(%d %d)=%d ",i,j,dp1[i][j]); } line(); } end();
		for(re int i=1; i<=n; i++)
		{
			//ans = 0;
			for(re int j=0; j<=k; j++)
			{ ans = fd(ans,1ll*str[k][j]*fac[j]%mod*fd(dp1[i][j],dp2[i][j])%mod); }
		}
		printf("%lld\n",1ll*ans*inv%mod);
		return 0;
	};
}
signed main()
{ return OMA::main(); }

T4

考场想的是线段树,发现自己不知道该如何维护对应信息,于是就暴力走人了。

60pts:

动态开点线段树。

对于树的每一层,也就是每一种深度都建一颗动态开点线段树,维护每个点的权值,修改直接暴跳 \(x\) ,对于跳到的每一层区间修改,查询就是单点查询。

因为是动态开点,点的标号不会重复,所以开一维数组即可。

原数据能过跑的比正解还快,但被战神加强数据后只剩60pts了。

100pts:分块+根号分治

正解竟然是分块???,菜了,根本不会做。

看了zero4338的blog改出来的.....%%% 。

先求出dfs序,这样就转换到了区间上的问题。

选择用线段树来维护每个点的权值。

对于线段树的每个节点维护一个 \(add1_{i,j}\) 表示区间内深度 \(\%i=j\) 的深度所增加的权值, 和\(add2_{i}\) 表示区间内能整除 \(x\) 的深度 \(i\) 所增加的权值。

\(x\le \sqrt{n}\) 时,用 \(add1\)\(x>\sqrt{n}\) 时,用 \(add2\)

为了方便 \(add1,add2\)\(map\) 来实现。

此题原数据其氵无比,线段树 \(mid\) 打成 \(l\) 都能过原数据,比随的还氵。

Code
#include<map>
#include<cmath>
#include<cctype>
#include<cstdio>
#include<unordered_map>
#define MAX 300005
#define re register
using std::map;
using std::unordered_map;
using std::pair;
using std::make_pair;
typedef pair<int,int> my;
namespace some
{
	struct stream
	{
		template<typename type>inline stream &operator >>(type &s)
		{
			bool w=0; s=0; char ch=getchar();
			while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
			while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
			return s=w?-s:s,*this;
		}
	}cin;
	struct graph
	{
		int next;
		int to;
	}edge[MAX<<1];
	int cnt=1,head[MAX];
	auto add = [](int u,int v) -> void { edge[++cnt] = (graph){head[u],v},head[u] = cnt; };
	auto max = [](int a,int b) -> int { return a>b?a:b; };
	auto debug = []() -> void { printf("fuck\n"); };
}using namespace some;
namespace OMA
{
	int n,q,len,xam;
	int dep[MAX],val[MAX];
	int lfn[MAX],rfn[MAX];
	void dfs(int u,int fa)
	{
		lfn[u] = ++cnt;
		xam = max(xam,val[cnt] = dep[u] = dep[fa]+1);
		for(re int i=head[u],v; i; i=edge[i].next)
		{
			v = edge[i].to;
			if(v!=fa)
			{ dfs(v,u); }
		}
		rfn[u] = cnt;
	}
	struct Tree
	{
		map<my,int>add1;
		unordered_map<int,int>add2;
		unordered_map<int,bool>vis;
	}st[MAX<<2];
	#define ls(p) p<<1
	#define rs(p) p<<1|1
	#define mid (l+r>>1)
	void modify(int p,int l,int r,int lp,int rp,int x,int y,int z)
	{
		if(lp<=l&&r<=rp)
		{
			if(x<=len)
			{ st[p].add1[make_pair(x,y)] += z,st[p].vis[x] = 1; }
			else
			{
				for(re int i=y; i<=xam; i+=x)
				{ st[p].add2[i] += z; }
			}
			return ;
		}
		if(lp<=mid)
		{ modify(ls(p),l,mid,lp,rp,x,y,z); }
		if(rp>mid)
		{ modify(rs(p),mid+1,r,lp,rp,x,y,z); }
	}
	int query(int p,int l,int r,int pos,int res = 0)
	{
		if(st[p].add2.find(val[pos])!=st[p].add2.end())
		{ res += st[p].add2[val[pos]]; }
		//printf("l=%d r=%d res=%d\n",l,r,res);
		for(auto it : st[p].vis)
		{
			my tmp = make_pair(it.first,val[pos]%it.first);
			if(st[p].add1.find(tmp)!=st[p].add1.end())
			{ res += st[p].add1[tmp]; }
		}
		//printf("l=%d r=%d res=%d\n",l,r,res);
		if(l==r)
		{ return res; }
		if(pos<=mid)
		{ return res+query(ls(p),l,mid,pos); }
		else
		{ return res+query(rs(p),mid+1,r,pos); }
	}
	auto main = []() -> signed
	{
		//freopen("node.in","r",stdin);
		//freopen("my.out","w",stdout);
		cin >> n >> q; len = sqrt(n);
		//printf("%d %d\n",n,q);
		for(re int i=1,u,v; i<=n-1; i++)
		{ cin >> u >> v; add(u,v),add(v,u); }
		cnt = 0; dfs(1,0); //debug();
		//for(re int i=1; i<=n; i++)
		//{ printf("lfn=%d rfn=%d dep=%d\n",lfn[i],rfn[i],dep[i]); }
		for(re int i=1,opt,v,x,y,z; i<=q; i++)
		{
			//debug();
			cin >> opt >> v;
			if(opt==1)
			{ cin >> x >> y >> z; modify(1,1,n,lfn[v],rfn[v],x,(y+dep[v])%x,z); }
			else
			{ printf("%d\n",query(1,1,n,lfn[v])); }
		}
		return 0;
	};
}
signed main()
{ return OMA::main(); }

没写分块,因为code奇长无比.....

posted @ 2021-09-15 11:28  -OMA-  阅读(83)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end