123789456ye

已AFO

2019暑假记录中

7.29 Day1

zjo学长的网络流

T1 (game)

  题意:Alice和Bob玩游戏,Alice先手。每一轮可以将一个数变为它的一个不是它自身和1的约数。无法行动者获胜。给出数n,问谁会赢。T组数据。\(T\leq 100,n\leq 10^{12}\)
  题解:分解质因数(因数也行),若正好有两个则Bob胜,否则Alice胜。线性筛出\(10^{6}\)的质数,剩余大于\(10^{6}\)的数时特判一下。
  复杂度\(O(\sqrt n)\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000005
long long prime[maxn];
int vis[maxn],cnt_prime,num;
inline void eular(int x)
{
	for(int i=2;i<=x;++i)
	{
		if(!vis[i]) prime[++cnt_prime]=i;
		for(int j=1;j<=cnt_prime&&i*prime[j]<=x;++j)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0) break;
		}
	}
}
inline bool factor(long long x)
{
	num=0;
	for(int i=1;i<=cnt_prime&&prime[i]<=x;++i)
	{
		if(x%prime[i]) continue;
		while(x%prime[i]==0)
		{
			x/=prime[i];
			++num;
		}
	}
	if(x>1000000) ++num;
	if(num==2) return 0;
	return 1;
}
int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	int T;
	long long a;
	scanf("%d",&T);
	eular(1000000);
	while(T--)
	{
		scanf("%lld",&a);
		if(factor(a)) puts("Alice");
		else puts("Bob");
	}
	return 0;
}

T2 (kun)

  题面:有n条鲲(手动滑稽),每只鲲有一个流量值a。m个鬼畜视频,每个视频可以消灭一只流量值小于等于d的鲲,有c的费用。求出消灭所有鲲的最小费用。若无法消除所有则输出“JiNiTaiMei!”(英文标点,不带引号)。所有数据\(\leq 10^{6}\)
  题解:桶排,相同值用链表存,每次找到离这个视频比它小的最近的鲲消灭掉。
  复杂度\(O(n\alpha(n))\)

#include<bits/stdc++.h>
using namespace std;
#define maxnn 1000005
int head[maxnn],fr[maxnn],fa[maxnn],t[maxnn],d[maxnn],c[maxnn];
long long ans;
inline int findf(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=findf(fa[x]);
}
inline void add(int st,int ed)
{
	fr[ed]=head[st];
	head[st]=ed;
}

int main()
{
	freopen("kun.in","r",stdin);
	freopen("kun.out","w",stdout);
	int n,m,maxn=0,maxm=0,kun;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%d",&kun),maxn=max(maxn,kun),++t[kun];
	for(int i=1;i<=maxn;++i)
	{
		if(t[i]) fa[i]=i;
		else fa[i]=findf(i-1);
	}
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d",&d[i],&c[i]);
		maxm=max(maxm,c[i]);
		if(d[i]>maxn) d[i]=maxn;
		add(c[i],i);
	}
	for(int i=0;i<=maxm;++i)
		if(head[i])
			for(int j=head[i];j;j=fr[j])
			{
				int tmp=findf(d[j]);
				if(!tmp) continue;
				--n,ans+=i;
				if(!--t[tmp]) fa[tmp]=findf(tmp-1);
				if(!n)
				{
					printf("%lld",ans);
					return 0;
				}
			}
	puts("JiNiTaiMei!");
	return 0;
}

T3 (canel)

  题意:给定\((n+1)*(m+1)\)的网格图,只能向右或向下走。第一行和最后一行分别有p个点。图中有q个点不能经过。求总方案数%998244353 \(n,m\leq 100000\) \(p,q\leq 200\)
  题解:直接蒯学长的题解吧。
  下面我们讨论在给定一对红蓝点之间有多少条线路.
  若不存在障碍点,一对点直接的路径条数显然是C(Δx+Δy,Δx),Δx、Δy分别为第一、二维坐标的差值.
  若存在障碍点,整张图是一个DAG,所以可以把中间的障碍点和蓝点进行拓扑排序.记F[i]表示从红点到第i个关键点,中间不经过障碍点的方案数,考虑容斥原理,f[i]=总方案数减去Σf[j]*j到i的方案数,j为拓扑序在i之前的点。
  对每对红蓝点间均应用上述过程,即得一个\(O(pq^{2})\)的算法解决不考虑交叉的问题。
  然后考虑路径交叉的情况,先预处理出way[i][j]表示第i个红点到第j个蓝点的方案数(先不考虑相交的情况)。这个可以\(O(pq^{2}+q^{2}q)\)的预处理出来。
  再次使用容斥原理,考虑每个红点与蓝点配对,总共有\(n!\)种方案.只有一种方案是合法方案(即按次序配对).但是这一种方案会出现相交路径,所以要减去有相交的路径。
  除了这一种方案之外的其他方案是必然有相交路径的,按照容斥原理,必然有两条路径相交的要减去,必然有三条路径相交的要加,以此类推。
  i按升序取1到p,对应顺序写下j序列,可以发现,若j序列的逆序对数为偶数,则加,否则则减。
  这个容斥的式子恰好与行列式的定义相吻合,所以只要把way数组看成行列式,然后求出这个行列式的值即可.
行列式的值的求法:根据行列式的性质,把行列式的任意一行(列)的元素乘以同一个数后,加到另一行(列)的互对应元素上去,行列式的值不变。然后就可以像高斯消元一样消成一个上三角,主对角线乘积就是答案。
总复杂度:\(O(p^{3}+pq^{2}+p^{2}q)\)
代码?又咕了

7.30 Day2 数学与概率期望

T1(buff) 和7.23 T3 一模一样 略

T2(pkd)(话说不是pdk吗?)

  题意:就是打跑得快只不过炸弹不算出牌次数
  给定一副牌,问最小出完牌的次数。多组数据。\(T\leq 1000,n\leq 23\)
  题解:搜索+dp。
  设\(f[i][j][k][l]\)表示能打4张的还有i种,3张的还有j种,以此类推。顺子直接爆搜。注意拆牌也可以转移。总共好像有十几种转移来着
  复杂度\(O(能过)\)
代码?又咕了

T3 (could)

  话说不应该是cloud吗
  题意:给定c,w。每个光顾商店的人有一个氪金值\(a_{i}\)和一个欧气值\(b_{i}\) ,若他的欧气值大于等于c,他会选择连抽c次获得卡片,若他的欧气值小于c,且他的氪金值大于等于卡片的价格p,他会氪金买他想要的卡片.否则他会离开商店.
  现在,游戏公司要做的就是定下需要连抽多少次才能保证获得想要的卡片,即一个人连抽c次游戏公司可以赚取\(c*w\)的利润。游戏公司想要你帮他计算c定为\(1\sim max(c_{i})+1\)时的最大利润。(p不确定)。\(n,a_{i},b_{i}\leq 100000\),\(w\leq 10000\)
  题解:又是蒯的
  按照\(b_{i}\)从小到大排序,依次枚举,i及以后所有人都会选择抽卡.
  现在要考虑的就是i以前的人,你需要确定一个p使得利润最大.
  设f[j]表示把p设为j,当前的最大利润,那么每次加入一个买卡的人,就是j属于\(1\sim a_{i}\) 的每个\(f[j]+=j\).
  所以题目转化为:写一个数据结构,支持以下几种操作:
  1.区间内每个点加上i
  2.询问区间最大值.
  传统的数据结构无法胜任,考虑分块.
  完整的块打标记,不完整的块直接暴力加.
  对于每一块,答案就是\(tag*i+a_{i}\)的最大值,把tag看成x,i看成k,因为k单调,tag每次+1,所以可以使用斜率优化来维护这一块的最优解.
  对于不完整块的修改,暴力重构即可.
  复杂度:\(O(n\sqrt n)\)
代码又咕了

7.31 Day 1

在vjudge上评测 全都是Codechef的题

A Xenny and Alternating Tasks

  题意:有两个人,n件事,两个人做每件事都有不同的时间。如果第一个人做第一件事,则奇数次都是他做,反之依然。\(n\leq 20000\)
  题解:纯模拟题。
  复杂度\(O(n)\)

#include<cstdio>
using namespace std;
int a[20005],b[20005];
inline int min(int x,int y)
{
	return x<y?x:y;
}
int main()
{
	int n,t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		for(int i=1;i<=n;++i)
			scanf("%d",&b[i]);
		int ans1=0,ans2=0;
		for(int i=1;i<=n;++i)
			ans1+=(i&1)?a[i]:b[i];
		for(int i=1;i<=n;++i)
			ans2+=(i&1)?b[i]:a[i];
		ans1=min(ans1,ans2);
		printf("%d\n",ans1);
	}
	return 0;
}

B Bear and Extra Number

  题意:有一段连续序列,每个数只出现了一次。现在加入了一个数,求出加入的数。\(n\leq 100000\)
  题解:直接排序扫一遍。
  复杂度\(O(n\log n)\)不过好像也能做到\(O(n)\)

#include<cstdio>
#include<algorithm>
using namespace std;
int a[100005],bucket[100005];
int main()
{
	int t,n;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		sort(a+1,a+n+1);
		if(a[1]+1!=a[2])
		{
			printf("%d\n",a[1]);
			continue;
		}
		if(a[n-1]+1!=a[n])
		{
			printf("%d\n",a[n]);
			continue;
		}
		for(int i=1;i<=n;++i)
			if(a[i]==a[i-1])
			{
				printf("%d\n",a[i]);
				break;
			}
	}
	return 0;
}

C Cooking Schedule

  题意:有一个长度为n的01序列,将其中至多k个位置进行反转,使序列中连续0或连续1的长度最小,并求出这个长度。\(n\leq 10^{6}\)
  题解:二分。预处理出每一段连续串的长度。对每一段序列,如果它的长度要小于等于x,则可以在其中反转\(\frac {len_{i}}{x+1}\)个使其符合要求。如果反转总数\(\leq k\)则符合要求。注意x=1时需要特判一下。
  复杂度\(O(n+\log n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
char s[1000005];
int num[1000005],cnt,cnt2,n,k;
inline bool chk()
{
	int ans=0;
	for(int i=1;i<=n;++i)
		if(s[i]-'0'==(i&1)) ++ans;
	return ans<=k||(n-ans)<=k;
}
inline bool check(int limit)
{
	int ans=0;
	for(int i=1;i<=cnt;++i)
		ans+=num[i]/(limit+1);
	return ans<=k;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&k);
		scanf("%s",s+1);
		if(chk())
		{
			puts("1");
			continue;
		}
		cnt=num[1]=1;
		for(int i=1;i<=n;++i)
		{
			if(s[i]==s[i-1]) ++num[cnt];
			else num[++cnt]=1;
		}
		int l=2,r=n,ans;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(check(mid)) r=mid-1,ans=mid;
			else l=mid+1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

D Subtree Removal

  给定n个节点的有根树(节点编号为 \(1\sim n\)),根节点为 1号节点。每个节点都有点权,记第i个节点的点权为Ai。\(n\leq 10^{5}\)
  你可以任意次(包括零次)进行下面的操作:选择树中的某个节点,并删去包括该节点在内
的整棵子树。
  记收益为树中剩下的节点的点权之和减去 X × k,其中 k 代表操作次数。求最大收益。
  题解:树形dp。设\(dp[i]\)表示以第i节点为根节点的最大收益,\(wei[i]\)为i子树点权和。
\(dp[i]=\max (\sum_{\text{for all v}}{dp[v]},-x-wei[i])\)
  答案为\(dp[1]+wei[1]\)
  复杂度\(O(n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 1000005
struct Edge
{
	int fr,to;
}eg[maxn<<1];
long long wei[maxn],dp[maxn];
int head[maxn],edgenum,x,n;
inline void add(int fr,int to)
{
	eg[++edgenum]=(Edge){head[fr],to};
	head[fr]=edgenum;
}
inline void dfs1(int now,int fa)
{
	for(int i=head[now];i;i=eg[i].fr)
	{
		if(eg[i].to==fa) continue;
		dfs1(eg[i].to,now);
		wei[now]+=wei[eg[i].to];
	}
}
inline void dfs2(int now,int fa)
{
	for(int i=head[now];i;i=eg[i].fr)
	{
		if(eg[i].to==fa) continue;
		dfs2(eg[i].to,now);
		dp[now]+=dp[eg[i].to];
	}
	dp[now]=max(dp[now],-x-wei[now]);
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&x);
		memset(head,0,sizeof(head));
		memset(dp,0,sizeof(dp));
		edgenum=0;
		for(int i=1;i<=n;++i)
			scanf("%lld",&wei[i]);
		int u,v;
		for(int i=1;i<n;++i)
			scanf("%d%d",&u,&v),add(u,v),add(v,u);
		dfs1(1,0),dfs2(1,0);
		printf("%lld\n",dp[1]+wei[1]);
	}
	return 0;
}

E Dish Owner

  题意:有n个厨师,每个人最开始都有一个菜,分值为s[i]。m个操作,0 x y表示x和y对决,谁手下分值最大的菜的分值最大谁胜。败者的菜归胜者所有。
1 x 表示查询编号x的菜在谁手上。\(n,m\leq 10000\)
  题解:显然如果这个人没被淘汰则它的分值最大的菜必定就是它一开始的菜。并查集维护即可。

#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
int fa[maxn],s[maxn];
inline int findf(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=findf(fa[x]);
}
int main()
{
	int n,t,q,op,x,y;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d",&s[i]),fa[i]=i;
		scanf("%d",&q);
		for(int i=1;i<=q;++i)
		{
			scanf("%d",&op);
			if(op)
			{
				scanf("%d",&x);
				printf("%d\n",findf(x));
			}
			else
			{
				scanf("%d%d",&x,&y);
				int fx=findf(x),fy=findf(y);
				if(fx==fy)
				{
					puts("Invalid query!");
					continue;
				}
				if(s[fx]==s[fy]) continue;
				if(s[fx]<s[fy]) fa[fx]=fy;
				else fa[fy]=fx;
			}
		}
	}
	return 0;
}

F Triplets

  题意:给定x,y,z三个序列,长度分别为p,q,r。
  求

\[(\sum_{i=1}^{p}{\sum_{j=1}^{q}{\sum_{k=1}^{r}{(x_{i}+y_{j})*(y_{j}+z_{k})*[x_{i}\leq y_{j}\&\&z_{k}<=y_{j}]}}})\mod 10^{9}+7 \]

\(p,q,r\leq 10^{6}\)
  题解:对三个序列排序,式子可以变为

\[\sum_{i=1}^{q}{y_i^2+sum_x*sum_z+sum_x*num_z+num_x*sum_z} \]

其中sum_x表示x序列的前缀和,num_x表示符合条件的x的数目,这个用upper_bound就好了。

#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 100005
#define ll long long
#define mod 1000000007
ll a[maxn],b[maxn],c[maxn],suma[maxn],sumc[maxn];
ll ans;
int main()
{
	//freopen("data.in","r",stdin);
	int t,p,q,r;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d",&p,&q,&r);
		for(int i=1;i<=p;++i)
			scanf("%lld",&a[i]);
		for(int i=1;i<=q;++i)
			scanf("%lld",&b[i]);
		for(int i=1;i<=r;++i)
			scanf("%lld",&c[i]);
		ans=0;
		sort(a+1,a+p+1);
		sort(b+1,b+q+1);
		sort(c+1,c+r+1);
		for(int i=1;i<=p;++i)
			suma[i]=suma[i-1]+a[i],suma[i]%=mod;
		for(int i=1;i<=r;++i)
			sumc[i]=sumc[i-1]+c[i],sumc[i]%=mod;
		for(int i=1;i<=q;++i)
		{
			int tp=upper_bound(a+1,a+p+1,b[i])-a-1;
			int tr=upper_bound(c+1,c+r+1,b[i])-c-1;
			ans+=(((b[i]*tp%mod)*b[i]%mod)*tr%mod);
			ans+=suma[tp]*sumc[tr]%mod;
			ans%=mod;
			ans+=((suma[tp]*tr+sumc[tr]*tp)%mod*b[i]%mod);
			ans%=mod;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

8.1 Day2

不是codechef的题已标出来

A Reign

  题意:给定序列,求所有相隔大于k的两个子串和的最大值。
  题解:先跑两遍最大子段和求出一个类似前缀和的东西(从前往后和从后往前),然后把从后往前的取个max,然后枚举分割点。具体看代码吧。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100005
#define inf 0x3f3f3f3f3f
ll num[maxn],head[maxn],tail[maxn];
int main()
{
	int t,n,k;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;++i)
			scanf("%lld",&num[i]);
		memset(head,0,sizeof(head));
		memset(tail,0,sizeof(tail));
		for(int i=1;i<=n;++i)
			head[i]=head[i-1]<0?num[i]:head[i-1]+num[i];
		head[0]=-inf;
		for(int i=1;i<=n;++i)
			head[i]=max(head[i-1],head[i]);
		for(int i=n;i;--i)
			tail[i]=tail[i+1]<0?num[i]:tail[i+1]+num[i];
		tail[n+1]=-inf;
		for(int i=n;i;--i)
			tail[i]=max(tail[i+1],tail[i]);
		ll ans=-inf;
		for(int i=1;i+k<n;++i)
			ans=max(ans,head[i]+tail[i+k+1]);
		printf("%lld\n",ans);
	}
	return 0;
}

B CF 1037E Trips

  题意:n个人,m天。最开始所有人都没有友谊。每一天都会有一对新的友谊关系。如果某一天有一群人,每个人都有k个朋友在其中,则这群人可以一起出去玩。求每一天能出去玩的人的数量。
注意:友谊不具有传递性。
  题解:考虑删边。当一个人的度数(入度+出度)小于k是删掉这个点并更新与之相连的点的度数。

#include<bits/stdc++.h>
using namespace std;
#define maxn 200005
set<int> s[maxn];
#define it set<int>::iterator
int cnt,deg[maxn],ans[maxn],vis[maxn];
int n,m,k;
int fr[maxn],to[maxn];
void del(int x)
{
	if(vis[x]||deg[x]>=k) return;
	queue<int> q;
	q.push(x);
	vis[x]=1;
	--cnt;
	while(!q.empty())
	{
		int tmp=q.front();
		q.pop();
		for(it i=s[tmp].begin();i!=s[tmp].end();++i)
			if(--deg[*i]<k&&!vis[*i])
			{
				q.push(*i);
				vis[*i]=1;
				--cnt;
			}
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	int u,v;
	cnt=n;
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d",&u,&v);
		++deg[u],++deg[v];
		s[u].insert(v),s[v].insert(u);
		fr[i]=u,to[i]=v;
	}
	for(int i=1;i<=n;++i) del(i);
	ans[m]=cnt;
	for(int i=m;i>1;--i)
	{
		if(!vis[to[i]]) --deg[fr[i]];
		if(!vis[fr[i]]) --deg[to[i]];
		s[fr[i]].erase(to[i]);
		s[to[i]].erase(fr[i]);
		del(fr[i]);
		del(to[i]);
		ans[i-1]=cnt;
	}
	for(int i=1;i<=m;++i)
		printf("%d\n",ans[i]);
	return 0;
}

C CF 475B Strongly Connected City

  题意:给定n条横边,m条竖边,问是否每一个交点都能到达其余任何一个交点。
  题解:可以用Tarjan。不过\(n,m\leq 20\)。显然直接对每个点bfs即可。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define pa pair<int,int>
char s[30];
struct Edge
{
	int fr,to;
}eg[5005];
int head[1000],edgenum;
inline void add(int fr,int to)
{
	eg[++edgenum]=(Edge){head[fr],to};
	head[fr]=edgenum;
}
int vis[1000];
void bfs(int s)
{
	memset(vis,0,sizeof(vis));
	queue<int> q;
	q.push(s);
	vis[s]=1;
	while(!q.empty())
	{
		int tmp=q.front();
		q.pop();
		for(int i=head[tmp];i;i=eg[i].fr)
		{
			if(vis[eg[i].to]) continue;
			vis[eg[i].to]=1;
			q.push(eg[i].to);
		}
	}
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	scanf("%s",s+1);
	for(int i=1;i<=n;++i)
	{
		if(s[i]=='<')
			for(int j=m;j>1;--j)
				add((i-1)*m+j,(i-1)*m+j-1);
		else
			for(int j=1;j<m;++j)
				add((i-1)*m+j,(i-1)*m+j+1);
	}
	scanf("%s",s+1);
	for(int i=1;i<=m;++i)
	{
		if(s[i]=='v')
			for(int j=0;j<n-1;++j)
				add(j*m+i,(j+1)*m+i);
		else
			for(int j=n-1;j>0;--j)
				add(j*m+i,(j-1)*m+i);
	}
	for(int i=1;i<=n*m;++i)
	{
		bfs(i);
		for(int j=1;j<=n*m;++j)
			if(!vis[j])
			{
				puts("NO");
				exit(0);
			}
	}
	puts("YES");
	return 0;
}

D Reach Equilibrium

  题意:有n个向量,随机向量模长使得模长之和为k。方向未定。求能使得这n个向量相加为0的概率。结果必定能写为\(p/q\)的形式。输出\(p*q^{-1}\mod 10^{9}+7\)
  题解:传送门

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 1000000007
ll a,b,x,y;
inline void exgcd(ll a,ll b,ll& x,ll& y)
{
	if(b==0)
	{
		x=1,y=0;
		return;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
inline ll qpow(ll a,int x)
{
	ll ans=1;
	while(x)
	{
		if(x&1) ans=(ans*a)%mod;
		a=(a*a)%mod;
		x>>=1;
	}
	return ans;
}
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	ll tmp=qpow(2,n-1);
	exgcd(tmp,mod,x,y);
	x=(x%mod+mod)%mod;
	printf("%lld\n",(tmp-n+mod)%mod*x%mod);
	return 0;
}

E Partition into Permutations

  题意:给定序列a。可以插入一个元素或删除一个元素。进行若干次修改后,可以使序列被划分为若干个集合。求修改次数最小值。集合定义为一个包含且仅包含一次\(1\sim n\)的序列。
  题解:设\(dp[i][j]\)表示i这个数应当出现j次。

\[dp[i][j]=min_{j\leq k}(dp[i-1][k])+abs(j-cnt_{i}) \]

  其中cnt表示i在原序列中出现的次数。

#include<bits/stdc++.h>
using namespace std;
#define maxn 2000005
int a[maxn],cnt[maxn];
vector<int> dp[maxn];

int main()
{
	int ans,t,n;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		ans=0;
		memset(cnt,0,sizeof(cnt));
		for(int i=1;i<=2*n;++i)
			dp[i].clear();
		for(int i=1;i<=n;++i)
		{
			scanf("%d",&a[i]);
			if(a[i]>(n<<1)) ++ans;
			else ++cnt[a[i]];
		}
		n<<=1;
		for(int i=1;i<=n;++i)
			dp[0].push_back(0);
		for(int i=1;i<=n;++i)
		{
			for(int j=0;j<=n/i;++j)
				dp[i].push_back(dp[i-1][j]+abs(j-cnt[i]));
			for(int j=n/i-1;j>=0;--j)
				dp[i][j]=min(dp[i][j+1],dp[i][j]);
		}
		ans+=min(dp[n][0],dp[n][1]);
		printf("%d\n",ans);
	}
	return 0;
}

F Chef and Digit Jumps

  题意:给定一个数字组成的字符串。每个位置i可以跳到i-1,i+1,和序列中与i相同的数的位置。求第一个数到最后一个数至少要跳几次。
  题解:spfa(bfs应该也行)。

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
char s[maxn];
int dis[maxn],vis[maxn],n;
vector<int> numb[10];
bool visnum[10];
void spfa(int st)
{
	queue<int> q;
	memset(dis,0x3f,sizeof(dis));
	q.push(st);
	vis[st]=1;
	dis[st]=0;
	while(!q.empty())
	{
		int tmp=q.front();
		q.pop();
		if(tmp==n) break;
		vis[tmp]=0;
		if(tmp!=1&&dis[tmp-1]>dis[tmp]+1)
		{
			dis[tmp-1]=dis[tmp]+1;
			if(!vis[tmp-1])
			{
				vis[tmp-1]=1;
				q.push(tmp-1);
			}
		}
		if(tmp!=n&&dis[tmp+1]>dis[tmp]+1)
		{
			dis[tmp+1]=dis[tmp]+1;
			if(!vis[tmp+1])
			{
				vis[tmp+1]=1;
				q.push(tmp+1);
			}
		}
		if(visnum[s[tmp]-'0']) continue;
		visnum[s[tmp]-'0']=1;
		for(int i=0;i<(int)numb[s[tmp]-'0'].size();i++)
			if(dis[numb[s[tmp]-'0'][i]]>dis[tmp]+1)
			{
				dis[numb[s[tmp]-'0'][i]]=dis[tmp]+1;
				if(!vis[numb[s[tmp]-'0'][i]])
				{
					vis[numb[s[tmp]-'0'][i]]=1;
					q.push(numb[s[tmp]-'0'][i]);
				}
			}
	}
}
int main()
{
	scanf("%s",s+1);
	while(s[++n]!='\0')
		numb[s[n]-'0'].push_back(n);
	--n;
	spfa(1);
	printf("%d\n",dis[n]);
	return 0;
}

8.2 Day1

T1 最大跨距(dis)

  题意:有三个字符串S,S1,S2。想检测S1和S2是否同时在S中出现,且S1位于S2的左边,并在S中互不交叉(即S1的右边界点在S2的左边界点的左侧)。计算满足上述条件的最大跨距(最右边的S2的起始点与最左边的S1的终止点之间的字符数目)。如果没有满足条件的S1,S2存在,则输出−1。
  题解:manacher模板
  不过scanf还能这么用……学到了

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
char s[maxn],s1[maxn],s2[maxn];
int next1[maxn],next2[maxn];
inline void pre(char* s,int* next)
{
	int j=0;
	for(int i=2;s[i];++i)
	{
		while(j&&s[i]!=s[j+1]) j=next[j];
		if(s[i]==s[j+1]) ++j;
		next[i]=j;
	}
}
int main()
{
	freopen("dis.in","r",stdin);
	freopen("dis.out","w",stdout);
	scanf("%[^,],%[^,],%[^,\n]",s+1,s1+1,s2+1);//读入字符串,当遇到^后面的字符时停止
	int len1=strlen(s1+1),len2=strlen(s2+1);
	int j=0,k=0,p1=0,p2=0;
	pre(s1,next1),pre(s2,next2);
	for(int i=1;s[i];++i)
	{
		while(j&&s[i]!=s1[j+1]) j=next1[j];
		while(k&&s[i]!=s2[k+1]) k=next2[k];
		if(s[i]==s1[j+1]) ++j;
		if(s[i]==s2[k+1]) ++k;
		if(j==len1&&!p1) p1=i;
		if(k==len2) p2=i;
	}
	if(p1&&p2&&p2>=len2+p1)
	{
		printf("%d\n",p2-len2-p1);
		return 0;
	}
	puts("-1");
	return 0;
}

T2 生日蛋糕(cake)

  题意:给定一个圆心为原点,半径为r的圆。给定n条直线,每根直线以两点坐标描述。所有数据均为整数。
  题解:咕了。
  代码?咕了。

T3 咖啡供应(coffee)

  题意:给定一棵树,在任意一点建一座咖啡站可使到这个点距离小于等于k的点被覆盖到。求覆盖所有点所需要的最少咖啡站数量。
  题解:贪心?不过考场上写挂了。
  先广搜,然后按广搜逆序求解。
  设\(dp[0][x]\)表示x点能覆盖到的最大深度,\(dp[1][x]\)表示能覆盖到x点的最大深度。
  转移方程看代码吧。

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000005
#define inf 0x3f3f3f3f
struct Edge
{
	int fr,to;
}eg[maxn<<1];
int head[maxn],edgenum;
int vis[maxn],dp[2][maxn],n,k,ans;
inline void add(int fr,int to)
{
	eg[++edgenum]=(Edge){head[fr],to};
	head[fr]=edgenum;
}
int q[maxn];
inline void bfs()
{
	int h=1,t=1;
	q[1]=1;
	while(h<=t)
	{
		int tmp=q[h++];
		vis[tmp]=1;
		for(int i=head[tmp];i;i=eg[i].fr)
			if(!vis[eg[i].to])
				q[++t]=eg[i].to;
	}
	for(int i=t;i;--i)
	{
		int tmp=q[i];
		vis[tmp]=0;
		dp[0][tmp]=-inf;
		for(int i=head[tmp];i;i=eg[i].fr)
		{
			int to=eg[i].to;
			if(!vis[to])
			{
				dp[0][tmp]=max(dp[0][tmp],dp[0][to]-1);
				dp[1][tmp]=max(dp[1][tmp],dp[1][to]+1);
			}
		}
		if(dp[1][tmp]<=dp[0][tmp]) dp[1][tmp]=-inf;
		if(dp[1][tmp]==k) dp[1][tmp]=-inf,dp[0][tmp]=k,++ans;
	}
	if(dp[1][1]!=-inf) ++ans;
}
int main()
{
	freopen("coffee.in","r",stdin);
	freopen("coffee.out","w",stdout);
	int u,v;
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;++i)
		scanf("%d%d",&u,&v),add(u,v),add(v,u);
	bfs();
	printf("%d\n",ans);
	return 0;
}

8.3 Day 2

T1 军队(tarmy)题意:给定长度为n的序列\(s_{i}\),\(k\),求满足

\[\sum_{i=l}^{r}{s_{i}}>=k\&\& \forall l\leq i<j \leq r,\gcd(s[i],s[j])==1 \]

  下\(r-l+1\)的最大值。若不存在则输出0。\(n\leq 10^{5},s_{i}\leq 10^{6},k\leq maxint\)
  题解:显然若\([l,r]\)不合法则\([l,r+1]\)必不合法。当合法时移动\(r\)并更新最大值,不合法时移动\(l\),若满足第一个条件则更新。判断合法则分解当前这个数的质因数,若某段区间某因子出现次数超过一则不合法。(合法指满足第二个条件)
  代码?咕咕咕
  复杂度:\(O(n\sqrt n)\)(好像也可以用什么pollard-rho做到\(O(n^{\frac{5}{4}})\)啊)
  由于数据过水,\(O(n^3\log n)\)也可通过此题,而且最大的一个点连0.1s都没有(设gcd的复杂度为log级别)

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

int a[maxn], sum[maxn];
inline int gcd(int a, int b)
{
	int tmp;
	while (b) tmp = b, b = a % b, a = tmp;
	return a;
}
inline bool check(int l, int r)
{
	for (int i = l; i < r; ++i)
		for (int j = i + 1; j <= r; ++j)
			if (gcd(a[i], a[j]) != 1) return false;
	return true;
}
int main()
{
	freopen("tarmy.in", "r", stdin);
	freopen("tarmy.out", "w", stdout);
	int n, k;
	scanf("%d%d", &n, &k);
	register int i;
	for (i = 1; i <= n; ++i)
		scanf("%d", &a[i]), sum[i] = a[i] + sum[i - 1];
	int maxl = 0;
	for (register int l = 1, r = 1; l <= n; l++)
	{
		while (sum[r] - sum[l - 1] < k && r <= n) r++;
		if (!check(l, r)) continue;
		maxl = max(maxl, r - l + 1);
		for (++r; r <= n; ++r)
		{
			if (!check(l, r)) break;
			maxl = max(maxl, r - l + 1);
		}
	}
	printf("%d\n", maxl);
	return 0;
}

T2 机器人采集金属(trobot)

  题意:给定一颗n节点有根树,路径有边权(代价)。有k个机器人,每个机器人可以在任意一点消失。问遍历所有点的代价的最小值。\(n\leq 5* 10^{4},k\leq 20\)
  题解:神仙树形dp。设\(dp[i][j]\)表示i节点j个机器人全都不回这个节点最少能省多少钱。显然最坏的情况是只有一个机器人,代价为所有边权和的两倍。
  $$dp[i][j]=\max (dp[i][j],dp[v][l]+dp[i][j-l]-(j-2)*val)$$
  其中\(v\)\(i\)的子节点,\(val\)\(i,v\)间的路径权值。\((j-2)*val\)是因为本来要走两遍,现在要走\(j\)遍。
  答案为\(sum*2-dp[s][k]\)\(sum\)为权值和。
  复杂度:\(O(nk^{2})\)

#include<bits/stdc++.h>
#pragma warning(disable:4576)
using namespace std;
#define maxn 50005
struct Edge
{
	int fr, to, val;
}eg[maxn << 1];
int head[maxn], edgenum, k;
inline void add(int fr, int to, int val)
{
	eg[++edgenum] = (Edge){ head[fr],to,val };
	head[fr] = edgenum;
}
int dp[maxn][21], vis[maxn], sum;
inline void dfs(int rt)
{
	vis[rt] = 1;
	for (int i = head[rt]; i; i = eg[i].fr)
	{
		int to = eg[i].to;
		if (vis[to]) continue;
		dfs(to);
		for (int l = k; l; --l)
			for (int j = 1; j <= l; ++j)
				dp[rt][l] = max(dp[rt][l], dp[rt][l - j] + dp[to][j] - (j - 2) * eg[i].val);
	}
}
int main()
{
	freopen("trobot.in","r",stdin);
	freopen("trobot.out","w",stdout); 
	int n, s, u, v, w;
	scanf("%d%d%d", &n, &s, &k);
	for (int i = 1; i < n; ++i)
	{
		scanf("%d%d%d", &u, &v, &w);
		sum += w;
		add(u, v, w);
		add(v, u, w);
	}
	dfs(s);
	printf("%d\n", sum * 2 - dp[s][k]);
	return 0;
}

T3 扔石头(tstones)

  题意:n块石头,每个人可以拿走\(1\sim k\)块,拿走最后一块的人获胜。给定n,k,求后手会不会赢。多组数据。\(n \leq 10^{18},T\leq 10^{6}\)
  题解:懒得写了。
  复杂度:\(O(1)\)

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
int main()
{
	freopen("tstones.in","r",stdin);
	freopen("tstones.out","w",stdout);
	int t;
	ull n,k;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%llu%llu",&n,&k);
		if(n%(k+1)!=0) puts("NO");
		else puts("YES");
	}
	return 0;
}

8.5 Day1

T1 同花顺(card)

  题意:给定n张牌,每张牌有花色\(a_{i}\)和点数\(b_{i}\),求使这些牌的\(a\)相同且\(b\)递增最少需换掉多少张牌。\(n\leq 10^{5},a_{i},b_{i}\leq 10^{9}\)
  题解:以\(a\)为第一关键字,\(b\)为第二关键字排序。之后找出同花色且最大点数减去最小点数小于等于\(n\)的序列的最大长度。这是因为若大于\(n\)则若不换掉最小或最大则不可能凑出同花顺。答案为总牌数减去长度。
  复杂度:\(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
#define f first
#define s second
pair<int,int> card[100005];
int main()
{
	freopen("card.in","r",stdin);
	freopen("card.out","w",stdout);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d%d",&card[i].f,&card[i].s);
	sort(card+1,card+n+1);
	int k=unique(card+1,card+n+1)-card-1,j,ans=0;
	for(int i=1;i<=k;++i)
	{
		j=i;
		while(card[i].f==card[j].f&&card[i].s-card[j].s+1<=n) --j;
		/*++j;
		ans=max(ans,i-j+1);*/
		ans=max(ans,i-j);
	}
	printf("%d\n",n-ans);
	return 0;
}

T2 做实验(test)

  题意:给定\(a_{i}\)\(b_{i}\),其中\(a_{i}\)的二进制展开表示一个集合,比如11表示{1,2,4}。对于每个\(a_{i},b_{i}\),求是\(a_{i}\)的非空子集且不是\(a_{i-b_{i}}\sim a_{i-1}\)的子集的集合数量。\(n,a_{i},b_{i}\leq 10^{5},b_{i}<i\)
  题解:对于\(a_{i}\),可以通过一种玄学操作求出其所有子集。记\(pos_{i}\)为这个子集的十进制表示\(i\)最后一次出现的位置。若\(pos_{i}<i-b\)则在这段区间内出现过,计入答案。
  复杂度:\({n\log n}<O(玄学)<O(n^{2})\)

#include<bits/stdc++.h>
using namespace std;
int pos[100005];
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	int ans,n,a,b;
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		ans=0;
		scanf("%d%d",&a,&b);
		for(int j=a;j;j=(j-1)&a)//玄学
		{
			if(pos[j]<i-b) ++ans;
			pos[j]=i;
		}
		printf("%d\n",ans);
	}
	return 0;
}

T3 拯救世界(save)(APIO2009劫掠计划)

  题意:给定有点权有向图,可以经过点和边无数次,给定起点,给定终点集合,求路线的最大点权和。\(n,m\leq 5*10^{5},sum\leq maxint\)
  题解:Tarjan缩点再topo排序再dp。注意某些地方起点无法到达但会对入度产生影响,需要特判。或者Tarjan完spfa求最长路。但是跑dfs会T飞(说的就是我)
  复杂度:\(O(n+m)\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 500005
struct Edge
{
	int fr,to;
}eg[maxn],eg2[maxn];
int head[maxn],head2[maxn],edgenum;
inline void add(int fr,int to,Edge* egg,int* headd)
{
	egg[++edgenum]=(Edge){headd[fr],to};
	headd[fr]=edgenum;
}
int n,m,S,P,p[maxn],w[maxn],u[maxn],v[maxn],degree[maxn],dp[maxn];
int dfn_clock,dfn[maxn],low[maxn],col[maxn],colnum,instack[maxn],colval[maxn],maxval[maxn];
stack<int> st;
inline void Tarjan(int s)
{
	low[s]=dfn[s]=++dfn_clock;
	st.push(s);
	instack[s]=1;
	for(int i=head[s];i;i=eg[i].fr)
	{
		int to=eg[i].to;
		if(!dfn[to])
		{
			Tarjan(to);
			low[s]=min(low[s],low[to]);
		}
		else if(instack[to]) low[s]=min(low[s],dfn[to]);
	}
	if(low[s]==dfn[s])
	{
		int tmp;
		++colnum;
		do
		{
			tmp=st.top();
			st.pop();
			instack[tmp]=0;
			col[tmp]=colnum;
		}while(s!=tmp);
	}
}
inline void DP(int s)
{
	queue<int> q;
	q.push(s);
	dp[s]=colval[s];
	while(!q.empty())
	{
		int tmp=q.front();
		q.pop();
		for(int i=head2[tmp];i;i=eg2[i].fr)
		{
			int to=eg2[i].to;
			dp[to]=max(dp[to],dp[tmp]+colval[to]);
			if(!--degree[to]) q.push(to);
		}
	}
}
int main()
{
	//freopen("save.in","r",stdin);
	//freopen("save.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d",&u[i],&v[i]);
		if(u[i]==v[i]) continue;
		add(u[i],v[i],eg,head);
	}
	for(int i=1;i<=n;++i)
		scanf("%d",&w[i]);
	scanf("%d%d",&S,&P);
	for(int i=1;i<=P;++i)
		scanf("%d",&p[i]);
	Tarjan(S);//特别处理一下
	for(int i=1;i<=n;++i)
		colval[col[i]]+=w[i];
	edgenum=0;
	for(int i=1;i<=m;++i)
	{
		if(col[u[i]]==col[v[i]]) continue;
		if(!col[u[i]]||!col[v[i]]) continue;//特判
		add(col[u[i]],col[v[i]],eg2,head2);
		++degree[col[v[i]]];
	}
	DP(col[S]);
	int ans=0;
	for(int i=1;i<=P;++i)
		ans=max(ans,dp[col[p[i]]]);
	printf("%d\n",ans);
	return 0;
}

8.6 Day2

T1 A 中位数图

  题意:给出\(1\sim n\)的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是\(b\)\(n\leq 10^{5}\)
  题解:把大于b的数看为1,小于b的看为-1。求出b的位置。在b前面从后往前扫一遍并开一个桶记录前缀和,后面从前往后扫一遍,答案加上当前前缀和的相反数的数量。
  复杂度:\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
int flag[maxn],bucket[maxn<<1],a[maxn];
int main()
{
	int n,b,sum=0,ans=1,pos;
	scanf("%d%d",&n,&b);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);
		if(a[i]>b) flag[i]=1;
		else if(a[i]<b) flag[i]=-1;
		else pos=i;
	}
	for(int i=pos-1;i>=1;--i)
	{
		sum+=flag[i];
		if(sum==0) ++ans;
		++bucket[sum+maxn];
	}
	sum=0;
	for(int i=pos+1;i<=n;++i)
	{
		sum+=flag[i];
		if(sum==0) ++ans;
		ans+=bucket[-sum+maxn];
	}
	printf("%d\n",ans);
	return 0;
}

T2 B 树

  题意:给定一个值S和一棵n节点树。在树的每个节点有一个正整数,问有多少条路径的节点总和为S。路径中节点的深度必须是升序的。\(n\leq 10^{5}\),保证不会在中间爆int。
  题解:直接暴力每个点往上跳。由于数据过水,倍增跳比暴力还慢
  复杂度:\(O(n^{2})\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
struct Edge
{
	int fr,to;
}eg[maxn<<1];
int head[maxn],edgenum,fa[maxn],wei[maxn];
int n,s;
inline void add(int fr,int to)
{
	eg[++edgenum]=(Edge){head[fr],to};
	head[fr]=edgenum;
}
inline void dfs_pre(int rt,int fat)
{
	for(int i=head[rt];i;i=eg[i].fr)
	{
		if(eg[i].to==fat) continue;
		fa[eg[i].to]=rt;
		dfs_pre(eg[i].to,rt);
	}
}
inline void solve()
{
	dfs_pre(1,0);
	int ans=0,sum;
	for(int i=1;i<=n;++i)
	{
		int tmp=i;
		sum=wei[i];
		while(sum<s)
		{
			tmp=fa[tmp],sum+=wei[tmp];
			if(tmp==0) break;
		}
		if(sum==s) ++ans;
	}
	printf("%d\n",ans);
}
int main()
{
	scanf("%d%d",&n,&s);
	for(int i=1;i<=n;++i)
		scanf("%d",&wei[i]);
	int x,y;
	for(int i=1;i<n;++i)
		scanf("%d%d",&x,&y),add(x,y),add(y,x);
	solve();
	return 0;
}

T3 C Bill的挑战

  题意:传送门(数据范围里的m就是n)
  题解:状压dp。
  设\(match[i][j]\)表示第i位为j时有哪些字符串可以匹配。(二进制下的第j位就表示第j个可以匹配)
  设\(dp[i][j]\)表示j集合(二进制压缩后)匹配到第i位的方案数。
  \(dp[i+1][match[i][ch]\&j]+=dp[i][j]\),其中ch为1~26,j为集合。初值\(dp[0][(1<<n)-1]=1\)
  统计答案则枚举集合数,若集合正好表示了k个字符串则计入答案。
  复杂度:\(O(2^{n}*26*len)\)

#include<bits/stdc++.h>
using namespace std;
char s[100][100];
int f[55][1<<15];
int n,k,len,tot,mod=1000003;
int match[50][50];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(f,0,sizeof(f));
        memset(match,0,sizeof(match));
        tot=0;
        scanf("%d%d",&n,&k);
        for (int i=1;i<=n;i++)
            scanf("%s",s[i]);
        len=strlen(s[1]);
        for(int i=0;i<len;i++)
            for(int ch='a';ch<='z';ch++)
                for(int j=1;j<=n;j++)
                    if(s[j][i]=='?'||s[j][i]==ch)
                        match[i][ch-'a']|=(1<<(j-1));
        int cnt=(1<<n)-1;
        f[0][cnt]=1;
        for(int i=0;i<len;i++)
            for(int j=0;j<=cnt;j++)
                if(f[i][j])
                    for(int ch='a';ch<='z';ch++)
					{
						f[i+1][match[i][ch-'a']&j]+=f[i][j];
                        f[i+1][match[i][ch-'a']&j]%=mod;
					}
        for(int i=0;i<=cnt;i++)
        {
            int ans=0;
            for(int j=1;j<=cnt;j<<=1) ans+=(i&j)?1:0;
            if(ans==k) tot=(tot+f[len][i])%mod;
        }
        printf("%d\n",tot);
    }
}

T4 D Problem c

  题意:给n个人安排座位,先给每个人一个1~n的编号,设第i个人的编号为ai(不同人的编号可以相同),接着从第一个人开始,大家依次入座,第i个人来了以后尝试坐到ai,如果ai被占据了,就尝试ai+1,ai+1 也被占据了的话就尝试ai+2,……,如果一直尝试到第n个都不行,该安排方案就不合法。然而有m个人的编号已经确定,你只能安排剩下的人的编号,求有多少种合法的安排方案。不合法则输出0。由于答案可能很大,只需输出其除以M后的余数即可。多组数据。\(t\leq 10,m\leq n\leq 300\)
  题解:先从后往前扫一遍m个人。如果编号大于等于i的人数已经超过了\(n-i+1\)则显然不合法。
  设\(dp[i][j]\)表示编号大于等于i的人已经安排了j个的方案数。
  \(dp[i][j]+=dp[i+1][j-k]*C_{j}^{k}\)表示安排好大于等于i+1中的j-k个后再安排k个人的编号为i。注意\(j\leq n-i-sum[i]+1\),其中\(sum[i]\)为之前扫的时候得出的数组。
  答案为\(dp[1][n-m]\)
  复杂度:\(O(n^{3})\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 305
#define ll long long
ll mod,dp[maxn][maxn],c[maxn][maxn],sum[maxn];
inline void pre(int x)
{
	c[0][0]=c[1][0]=c[1][1]=1;
	for(int i=2;i<=x;++i)
	{
		c[i][0]=1;
		for(int j=1;j<=i;++j)
			c[i][j]=c[i-1][j]+c[i-1][j-1],c[i][j]%=mod;
	}
}					
int main()
{
	int T,n,m;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%lld",&n,&m,&mod);
		int x,y;
		memset(sum,0,sizeof(sum));
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=m;++i)
			scanf("%d%d",&x,&y),++sum[y];
		for(int i=n;i;--i)
		{
			sum[i]+=sum[i+1];
			if(sum[i]>n-i+1)
			{
				puts("NO");
				goto end;
			}
		}
		pre(n);
		dp[n+1][0]=1;
		for(int i=n;i;--i)
			for(int j=0;j<=n-sum[i]-i+1;++j)
				for(int k=0;k<=j;++k)
					dp[i][j]=(dp[i][j]+dp[i+1][j-k]*c[j][k])%mod;
		printf("YES %lld\n",dp[1][n-m]);
	  end:;
	}
	return 0;
}

T5 E 松鼠的新家

  题意:给定序列\(a[i]\)和一颗n节点的树。初始点权为0。对于每个\(a[i]]\to a[i+1]\),路径上除\(a[i]\)以外的所有点点权加1。\(a[1]\)点权+1,\(a[n]\)点权-1,求每个点的点权。\(n\leq 3*10^{6}\)
  题解:树剖模板。然而早已切了这道题的我调了2.5h仍然没调出来,最后只好蒯我之前写的
  复杂度:\(O(n\log n)\)
  upd:pushdown少打了一个加号……调了整个晚自习终于调出来了。自闭了。

#include<bits/stdc++.h>
using namespace std;
#define maxn 300005
struct Edge
{
    int fr,to;
}eg[maxn<<1];
int a[maxn],head[maxn];
int deep[maxn],size[maxn],son[maxn],top[maxn],id[maxn],fa[maxn],ans[maxn];
int edgenum,cnt,n;
inline void add(int fr,int to)
{
    eg[++edgenum]=(Edge){head[fr],to};
    head[fr]=edgenum;
}
struct SegTree
{
    int l,r,val,lazy;
}node[maxn<<2];
inline void pushup(int rt)
{
    node[rt].val=node[rt<<1].val+node[rt<<1|1].val;
}
inline void build(int rt,int l,int r)
{
    node[rt].l=l,node[rt].r=r;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
}
inline void pushdown(int rt)
{
    if(node[rt].lazy)
    {
        node[rt<<1].lazy+=node[rt].lazy;
        node[rt<<1|1].lazy+=node[rt].lazy;
        node[rt<<1].val+=node[rt].lazy*(node[rt<<1].r-node[rt<<1].l+1);
        node[rt<<1|1].val+=node[rt].lazy*(node[rt<<1|1].r-node[rt<<1|1].l+1);
        node[rt].lazy=0;
    }
}
inline void update(int rt,int l,int r,int fr,int to,int val)
{
    if(fr<=l&&to>=r)
    {
        node[rt].val+=val*(r-l+1);
        node[rt].lazy+=val;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(rt);
    if(fr<=mid) update(rt<<1,l,mid,fr,to,val);
    if(to>mid) update(rt<<1|1,mid+1,r,fr,to,val);
    pushup(rt);
}
inline int query(int rt,int l,int r,int fr,int to)
{
    if(fr<=l&&to>=r)
        return node[rt].val;
    pushdown(rt);
    int mid=(l+r)>>1,ans=0;
    if(fr<=mid) ans+=query(rt<<1,l,mid,fr,to);
    if(to>mid) ans+=query(rt<<1|1,mid+1,r,fr,to);
    return ans;
}
inline void dfs1(int s,int fat,int dep)
{
    fa[s]=fat;
    size[s]=1;
    deep[s]=dep;
    int maxson=-1;
    for(int i=head[s];i;i=eg[i].fr)
    {
        int to=eg[i].to;
        if(to==fat) continue;
        dfs1(to,s,dep+1);
        size[s]+=size[to];
        if(size[to]>maxson) maxson=size[to],son[s]=to;
    }
}
inline void dfs2(int s,int topp)
{
    top[s]=topp;
    id[s]=++cnt;
    if(!son[s]) return;
    dfs2(son[s],topp);
    for(int i=head[s];i;i=eg[i].fr)
    {
        int to=eg[i].to;
        if(to==fa[s]||to==son[s]) continue;
        dfs2(to,to);
    }
}
inline void updateroad(int x,int y,int val)
{
    while(top[x]!=top[y])
    {
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        update(1,1,n,id[top[x]],id[x],val);
        x=fa[top[x]];
    }
    if(deep[x]>deep[y]) swap(x,y);
    update(1,1,n,id[x],id[y],val);
}
inline int randm(int l,int r)
{
    int ans=1;
    for(int i=1;i<=6;++i)
        ans*=10,ans+=rand()%10;
    ans=(ans%(r-l)+l);
    return ans;
}
int main()
{
	freopen("testdata.in","r",stdin);
	freopen("testdata.out","w",stdout);
	srand(time(NULL));
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]);
    int x,y,r=randm(1,n);
    for(int i=1;i<n;++i)
        scanf("%d%d",&x,&y),add(x,y),add(y,x);
	r=1;
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,1,n);
    for(int i=1;i<n;++i)
        updateroad(a[i],a[i+1],1);
    for(int i=1;i<=n;++i)
        ans[i]=query(1,1,n,id[i],id[i]);
    for(int i=2;i<=n;++i)
        --ans[a[i]];
    for(int i=1;i<=n;++i)
		printf("%d\n",ans[i]);
    return 0;
}
posted @ 2019-08-04 18:54  123789456ye  阅读(24)  评论(0)    收藏  举报