2020牛客暑期多校训练营(第九场)题解

A. Groundhog and 2-Power Representation

给出一个字符串,其中2(x)表示 2 2 2 x x x 次方, x x x 只可能是 0 0 0 或表达式,其中还可能有 + + + 表示加法,计算这个式子的值。

直接递归求解即可,能一发过样例然后调大数组就AC我是没想到的,蒟蒻觉得可能要调试很久来着……

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

char s[20010];int n;
struct GJD{
    
    
	int a[310],t;
	GJD(){
    
    memset(a,0,sizeof(a));t=1;}
	void update(){
    
    
		for(int i=1;i<=t;i++)if(a[i]>9){
    
    
			a[i+1]+=a[i]/10;
			a[i]%=10;
			if(i==t)t++;
		}
	}
	int &operator [](int x){
    
    return a[x];}
	GJD operator +(GJD B){
    
    
		GJD re;re.t=max(t,B.t);
		for(int i=1;i<=re.t;i++)re.a[i]=a[i]+B[i];
		re.update();return re;
	}
	GJD operator *(GJD &B){
    
    
		GJD re;re.t=t+B.t-1;
		for(int i=1;i<=t;i++){
    
    
			for(int j=1;j<=B.t;j++)
			re.a[i+j-1]+=a[i]*B[j];
		}
		re.update();return re;
	}
	void div2(){
    
    
		for(int i=1;i<=t;i++){
    
    
			if(i>1)a[i-1]+=a[i]&1?5:0;
			a[i]/=2;
		}
		while(t>1&&!a[t])t--;
	}
	void print(){
    
    for(int i=t;i>=1;i--)printf("%d",a[i]);printf("\n");}
};
struct par{
    
    int endpos;GJD re;};
GJD ksm(GJD y){
    
    
	GJD re;re[1]=1;
	GJD x;x[1]=2;
	while(y.t>1||y[1]){
    
    
		if(y[1]&1)re=re*x;
		x=x*x;y.div2();
	}
	return re;
}
par dfs(int pos){
    
    
	GJD re;
	for(;;pos++){
    
    
		if(s[pos]==')')break;
		if(s[pos]=='2'){
    
    
			if(s[pos+1]=='('){
    
    
				par p=dfs(pos+2);
				re=re+ksm(p.re);
				pos=p.endpos;
			}else re[1]+=2;
		}
	}
	re.update();
	return (par){
    
    pos,re};
}

int main()
{
    
    
	scanf("%s",s+1);
	n=strlen(s+1);s[++n]=')';s[n+1]=0;
	par ans=dfs(1);ans.re.print();
}

B. Groundhog ans Apple Tree

有一棵苹果树,每个节点上有一个苹果,苹果可以回复HP,经过一条边会损耗HP,HP任意时刻不能小于 0 0 0,每条边只能经过两次,休息 1 s 1s 1s 可以增加 1 1 1 HP,问最少休息时间。

a X a_X aX 表示走完 X X X 子树后HP的变化量, b X b_X bX 表示走 X X X 的子树过程中HP的最低值,换言之, − b X -b_X bX 也就是需要在 x x x 处通过休息回复的HP值。

s o n son son X X X 的儿子们的一个排列,容易发现, b X = min ⁡ i = 1 ∣ s o n ∣ { ( ∑ j = 1 i − 1 a s o n j ) + b s o n i } b_X=\min_{i=1}^{|son|}\{(\sum_{j=1}^{i-1}a_{son_j})+b_{son_i}\} bX=mini=1son{ (j=1i1asonj)+bsoni}

要使 b X b_X bX 尽可能大,只能通过调整访问儿子的顺序,对于两个访问顺序相邻的儿子 x , y x,y x,y,如果先访问 x x x 再访问 y y y 更优,那么一定满足:
min ⁡ ( b x , a x + b y ) > min ⁡ ( b y , a y + b x ) \min(b_x,a_x+b_y)>\min(b_y,a_y+b_x) min(bx,ax+by)>min(by,ay+bx)

考虑 a x , a y a_x,a_y ax,ay 符号不同时,假如 a x > 0 , a y < 0 a_x>0,a_y<0 ax>0,ay<0 那么显然成立。

再考虑他们符号相同时,若 a x , a y ≥ 0 a_x,a_y\geq 0 ax,ay0,通过讨论 b x b_x bx a x + b y a_x+b_y ax+by 之间的大小关系,可以知道此时只有满足 b x > b y b_x>b_y bx>by 才能使上式成立。

类似的,当 a x , a y < 0 a_x,a_y<0 ax,ay<0 时,只有当 a x + b y > a y + b x a_x+b_y>a_y+b_x ax+by>ay+bx 时,即 a x − b x > a y − b y a_x-b_x>a_y-b_y axbx>ayby 时才成立。

于是排个序再依次遍历就做完了,代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010
#define ll long long
#define pb push_back

int T,n;
int val[maxn];
vector<int> e[maxn],w[maxn];
ll a[maxn],b[maxn];
bool cmp(int x,int y){
    
    
	if(a[x]>=0&&a[y]>=0)return b[x]>b[y];
	else if(a[x]<0&&a[y]<0)return a[x]-b[x]>a[y]-b[y];
	else return a[x]>a[y];
}
void dfs(int x,int fa){
    
    
	a[x]=val[x];b[x]=0;
	for(int i=0;i<e[x].size();i++)if(e[x][i]!=fa){
    
    
		int y=e[x][i],z=w[x][i];
		dfs(y,x);
		a[y]-=2ll*z;b[y]-=z;
		b[y]=min(b[y],a[y]);
	}
	sort(e[x].begin(),e[x].end(),cmp);
	for(int y:e[x])if(y!=fa){
    
    
		b[x]=min(b[x],a[x]+b[y]);
		a[x]+=a[y];
	}
}

int main()
{
    
    
	scanf("%d",&T);while(T--)
	{
    
    
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
    
    
			scanf("%d",&val[i]);
			e[i].clear();w[i].clear();
		}
		for(int i=1,x,y,z;i<n;i++){
    
    
			scanf("%d %d %d",&x,&y,&z);
			e[x].pb(y);w[x].pb(z);
			e[y].pb(x);w[y].pb(z);
		}
		dfs(1,0);
		printf("%lld\n",max(0ll,-b[1]));
	}
}

C. Groundhog ans Gmaming Time

给出 n n n 条线段,每条线段有 1 2 \frac 1 2 21 的概率被选择,问选择的线段的并的长度的平方的期望。

n n n 条线段会分出 2 n − 1 2n-1 2n1 个小区间,考虑对每个小区间求解。

由于求的是长度平方的期望,所以一个小区间的贡献是:小区间的长度 × \times × 包含这个小区间的线段的并的长度的期望 E E E

假设现在在求解第 i i i 个小区间,令 c j c_j cj 表示同时包含 i , j i,j i,j 区间的线段数量,那么第 j j j 个区间对 E E E 的贡献为 2 c j − 1 2 n × l j \dfrac {2^{c_j}-1} {2^n}\times l_j 2n2cj1×lj l j l_j lj 表示第 j j j 个小区间长度,那么第 i i i 个小区间的贡献为 l i × ∑ j = 1 2 n − 1 2 c j − 1 2 n × l j l_i\times\sum_{j=1}^{2n-1}\dfrac {2^{c_j}-1} {2^n}\times l_j li×j=12n12n2cj1×lj,用线段树维护 2 c j × l j 2^{c_j}\times l_j 2cj×lj 即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 1000010
#define mod 998244353
#define pb push_back

int n;
struct par{
    
    int x,y,z;}a[maxn];
vector<int> b;
const int inv2=(mod+1)/2;
int add(int x){
    
    return x>=mod?x-mod:x;}
struct node{
    
    
	int l,r,mid,val,c;node *zuo,*you;
	node(int x,int y):l(x),r(y),mid(l+r>>1),val((b[y+1]-b[x])%mod),c(1){
    
    
		if(x<y){
    
    
			zuo=new node(l,mid);
			you=new node(mid+1,r);
		}else zuo=you=NULL;
	}
	void update(int z){
    
    c=1ll*c*z%mod;val=1ll*val*z%mod;}
	void change(int x,int y,int z){
    
    
		if(l==x&&r==y)return update(z);
		if(y<=mid)zuo->change(x,y,z);
		else if(x>=mid+1)you->change(x,y,z);
		else zuo->change(x,mid,z),you->change(mid+1,y,z);
		val=1ll*add(zuo->val+you->val)*c%mod;
	}
}*root;
vector<par> d[maxn];

int main()
{
    
    
	scanf("%d",&n);
	b.pb(0);b.pb(2e9);
	for(int i=1;i<=n;i++){
    
    
		scanf("%d %d",&a[i].x,&a[i].y);
		b.pb(a[i].x),b.pb(++a[i].y);
	}
	sort(b.begin(),b.end());b.erase(unique(b.begin(),b.end()),b.end());
	#define desc(x) (lower_bound(b.begin(),b.end(),x)-b.begin())
	for(int i=1;i<=n;i++){
    
    
		a[i].x=desc(a[i].x);a[i].y=desc(a[i].y);
		d[a[i].x].pb((par){
    
    a[i].x,a[i].y-1,2});
		d[a[i].y].pb((par){
    
    a[i].x,a[i].y-1,inv2});
	}
	int ans=0;root=new node(0,b.size()-2);
	for(int i=0;i<b.size()-1;i++){
    
    
		for(par j:d[i])root->change(j.x,j.y,j.z);
		ans=add(ans+1ll*(b[i+1]-b[i])*root->val%mod);
	}
	ans=(ans-((long long)4e18)%mod+mod)%mod;
	for(int i=1;i<=n;i++)ans=1ll*ans*inv2%mod;
	printf("%d",ans);
}

D. Groundhog and Golden Apple

有一棵树,第 i i i 个节点上有一个编号为 i i i 的人,第 i i i 个人有 K i K_i Ki 次强行突破的权利,每条边有一个允许区间,如果一个人的编号在允许区间内,那么他可以直接通过这条边,否则就要强行突破,问每个人能够到达多少个点。

转化一下,对于第 i i i 个人,假如他在一条边的允许区间内,那么这条边边权为 0 0 0,否则为 1 1 1,那么权为 0 0 0 的边组成了一些连通块,若 K i = 0 K_i=0 Ki=0,那么答案就是 i i i 所在连通块的大小,否则还要加上相邻连通块的大小,可以用可撤销按秩合并并查集加分治来维护连通块。

维护连通块的同时还需要维护答案,不妨让点 1 1 1 为根,令 s 1 [ i ] s1[i] s1[i] 表示 i i i 连通块的大小, s 2 [ i ] s2[i] s2[i] 表示 i i i 的儿子连通块大小总和,那么每次合并连通块时,只需要更新秩大的块的 s 1 , s 2 s1,s2 s1,s2 以及较浅的连通块的父亲的 s 2 s2 s2 即可。

代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200010
#define pb push_back

int n,K[maxn],ans[maxn];
vector<int> e[maxn];
struct OP{
    
    int x,y;};
vector<OP> op[maxn<<1];
void add(int id,int l,int r,int L,int R,int x,int y){
    
    
	if(l==L&&r==R)return op[id].pb((OP){
    
    x,y});
	int mid=l+r>>1;
	if(R<=mid)add(id<<1,l,mid,L,R,x,y);
	else if(L>=mid+1)add((id<<1)+1,mid+1,r,L,R,x,y);
	else add(id<<1,l,mid,L,mid,x,y),add((id<<1)+1,mid+1,r,mid+1,R,x,y);
}
int dep[maxn],Fa[maxn],top[maxn];
int s1[maxn],s2[maxn],fa[maxn];
int findfa(int x){
    
    return x==fa[x]?x:findfa(fa[x]);}
void dfs(int x){
    
    
	s1[x]=1;fa[x]=x;top[x]=x;
	for(int y:e[x])if(y!=Fa[x]){
    
    
		Fa[y]=x;s2[x]++;
		dep[y]=dep[x]+1;
		dfs(y);
	}
}
struct par{
    
    int x,y,s1_x,s2_x,top_x,fa_x,s2_fa;}zhan[maxn<<3];int t=0;
void solve(int id,int l,int r){
    
    
	int last=t;
	for(OP i:op[id]){
    
    
		int x=i.x,y=i.y;if(dep[y]<dep[x])swap(x,y);
		x=findfa(x),y=findfa(y);int father=findfa(Fa[top[x]]);
		if(s1[x]>=s1[y]){
    
    
			zhan[++t]=(par){
    
    x,y,s1[x],s2[x],top[x],father,s2[father]};
			s2[father]+=s1[y];
			s2[x]+=s2[y]-s1[y];
			s1[x]+=s1[y];
			fa[y]=x;
		}else{
    
    
			zhan[++t]=(par){
    
    y,x,s1[y],s2[y],top[y],father,s2[father]};
			s2[father]+=s1[y];
			s2[y]+=s2[x]-s1[y];
			s1[y]+=s1[x];
			fa[x]=y;
			top[y]=top[x];
		}
	}
	if(l==r){
    
    
		int x=findfa(l);
		if(!K[l])ans[l]=s1[x];
		else ans[l]=s1[x]+s2[x]+s1[findfa(Fa[top[x]])];
	}else{
    
    
		int mid=l+r>>1;
		solve(id<<1,l,mid);
		solve((id<<1)+1,mid+1,r);
	}
	while(t>last){
    
    
		int x=zhan[t].x,y=zhan[t].y;
		s1[x]=zhan[t].s1_x;
		s2[x]=zhan[t].s2_x;
		top[x]=zhan[t].top_x;
		fa[y]=y;
		s2[zhan[t].fa_x]=zhan[t].s2_fa;
		t--;
	}
}

int main()
{
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&K[i]);
	for(int i=1,x,y,l,r;i<n;i++){
    
    
		scanf("%d %d %d %d",&x,&y,&l,&r);
		e[x].pb(y);e[y].pb(x);
		add(1,1,n,l,r,x,y);
	}
	dfs(1);solve(1,1,n);
	for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
}

E. Groundhog Chasing Death

给出 l 1 , r 1 , l 2 , r 2 , a , b l_1,r_1,l_2,r_2,a,b l1,r1,l2,r2,a,b,计算 ∏ i = l 1 r 1 ∏ j = l 2 r 2 gcd ⁡ ( a i , b j ) \prod_{i=l_1}^{r_1}\prod_{j=l_2}^{r_2} \gcd(a^i,b^j) i=l1r1j=l2r2gcd(ai,bj)

因为 a a a 最大 1 e 9 1e9 1e9,拥有的不同质因子个数不超过 9 9 9 个,即 a , b a,b a,b 共同拥有的质因子个数不超过 9 9 9 个,考虑对每个质因子单独求解。

对于质因子 p p p,设 a a a 包含 x x x p p p b b b 包含 y y y p p p,那么就是要计算:
p ∑ i ∑ j min ⁡ ( i x , j y ) p^{\sum_i\sum_j\min(ix,jy)} pijmin(ix,jy)

i x ix ix 为较小值,枚举 i i i,容易算出 j j j 的范围,然后就可以统计出来。 j y jy jy 为最小值的情况类似。

代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define mod 998244353

int l1,r1,l2,r2,a,b;
struct par{
    
    int p,x,y;};
vector<par> A,B,C;
void solve(int x,vector<par> &vec){
    
    
	for(int i=2;i*i<=x;i++){
    
    
		if(x%i==0){
    
    
			int tot=0;
			while(x%i==0)x/=i,tot++;
			vec.push_back((par){
    
    i,tot,0});
		}
	}
	if(x>1)vec.push_back((par){
    
    x,1,0});
}
int ksm(int x,int y){
    
    int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int run(int p,int x,int y){
    
    
	int re=0;
	for(int i=l1;i<=r1;i++){
    
    
		int lim=max(l2,(int)ceil(1.0*i*x/y));//注意,这里求的是指数之和,所以模数为mod-1
		if(lim<=r2)re=(re+1ll*(r2-lim+1)*i%(mod-1)*x%(mod-1))%(mod-1);
	}
	for(int j=l2;j<=r2;j++){
    
    
		int lim=max(l1,(int)ceil(1.0*j*y/x+1e-5));//+1e-5是去掉ix=jy的情况,这种情况上面计算过了
		if(lim<=r1)re=(re+1ll*(r1-lim+1)*j%(mod-1)*y%(mod-1))%(mod-1);
	}
	return ksm(p,re);
}

int main()
{
    
    
	scanf("%d %d %d %d %d %d",&l1,&r1,&l2,&r2,&a,&b);
	solve(a,A);solve(b,B);
	for(par i:A)for(par j:B)if(i.p==j.p){
    
    C.push_back((par){
    
    i.p,i.x,j.x});break;}
	int ans=1;for(par i:C)ans=1ll*ans*run(i.p,i.x,i.y)%mod;
	printf("%d",ans);
}

F. Groundhog Looking Dowdy

n n n 天,每天给 k i k_i ki 件衣服,每件衣服有一个邋遢值,你要选 m m m 天,每天选一件衣服,使选出来的衣服的邋遢值的最大值与最小值之差最小。

一开始以为他不会卡堆,就直接用set维护一波信息,然后就T飞了……

后来发现,将所有衣服按邋遢值从小到大排序之后,就变成了:选一个区间,区间内邋遢值最大最小值差值最小,并且出现过 m m m 天的衣服。这就是个简单的尺取法了。

代码如下:

#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;
#define maxn 1000010

int n,m,ans=1e9;
struct par{
    
    int x,be;};
vector<par> a;
int occ[maxn],tot=0;
bool cmp(par x,par y){
    
    return x.x<y.x;}

int main()
{
    
    
	scanf("%d %d",&n,&m);
	for(int i=1,k,x;i<=n;i++){
    
    
		scanf("%d",&k);
		while(k--)scanf("%d",&x),a.push_back((par){
    
    x,i});
	}
	sort(a.begin(),a.end(),cmp);
	for(int l=0,r=0;l<=n;l++){
    
    
		while(r<a.size()&&tot<m)if(!occ[a[r++].be]++)tot++;
		if(tot<m)break;
		ans=min(ans,a[r-1].x-a[l].x);
		if(!--occ[a[l].be])tot--;
	}
	printf("%d",ans);
}

G. Groundhog Playing Scissors

给出一个凸多边形,可以绕 ( 0 , 0 ) (0,0) (0,0) 旋转,给一条直线,问将多边形随机旋转后,直线被凸多边形包含的部分长度超过 L L L 的概率。

旋转凸多边形等价于旋转直线,将角度分成若干小份,将直线依次旋转然后求与凸多边形两个交点的距离即可。

同校大佬分了 3 × 1 0 5 3\times 10^5 3×105 块就过了,但是蒟蒻要分大约 6 × 1 0 6 6\times 10^6 6×106 块并开一手long double才能过,很奇怪……(值得高兴的是,我虽然理论复杂度比他高几十倍,但是实际只多了1.5倍的时间,说明蒟蒻实现的还算优秀qwq……)

代码如下:

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 1000010
#define double long double

const double eps=1e-10;
const double Pi=acos(-1);
struct point{
    
    
	double x,y; point(){
    
    }
	point(double xx,double yy):x(xx),y(yy){
    
    }
	point operator +(const point B){
    
    return point(x+B.x,y+B.y);}
	point operator -(const point B){
    
    return point(x-B.x,y-B.y);}
	point operator *(const double B){
    
    return point(x*B,y*B);}
	double operator ^(const point B){
    
    return x*B.x+y*B.y;}//点积
	double operator *(const point B){
    
    return x*B.y-y*B.x;}//叉积
};
double dis(point a,point b){
    
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}
struct Vec{
    
    
	point x,y; Vec(){
    
    }
	Vec(point xx,point yy):x(xx),y(yy){
    
    }
	double len(){
    
    return dis(x,y);}
};
//判断直线是否穿过线段
bool cross2(Vec a,Vec b){
    
    return ((b.x-a.x)*(a.y-a.x))*((b.y-a.x)*(a.y-a.x))<=0;}
bool cross(Vec a,Vec b){
    
    return cross2(a,b)&&cross2(b,a);}
point meet(Vec a,Vec b){
    
    //求直线交点
	if(fabs((a.y-a.x)*(b.y-b.x))<eps)return point(-1e9,-1e9);
//	if(!cross(a,b))return point(-1e9,-1e9);
	point A=a.y-a.x,B=b.y-b.x,C=b.x-a.x;
	double k=(B*C)/(B*A);
	return a.x+A*k;
}
point Turn(point a,double Angle,double S=-3,double C=-3){
    
    //将点旋转Angle度
	if(S==-3)S=sin(Angle),C=cos(Angle);
	return point(a.x*C-a.y*S,a.y*C+a.x*S);
}
int n;
point sp[maxn];
Vec d[maxn];
double L;
Vec Li;

int main()
{
    
    
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=0;i<n;i++)cin>>sp[i].x>>sp[i].y;
	for(int i=0;i<n;i++)d[i]=Vec(sp[i],sp[(i+1)%n]);
	cin>>L>>Li.x.x>>Li.x.y>>Li.y.x>>Li.y.y;
	int now1=0,now2;
	while(!cross2(Li,d[now1]))now1=now1+1==n?0:now1+1;
	now2=now1+1;
	while(!cross2(Li,d[now2]))now2=now2+1==n?0:now2+1;
	int ans=0;
	const double sz=1e-6,S=sin(sz),C=cos(sz);
	int Times=ceil(2*Pi/sz);
	for(int i=1;i<=Times;i++){
    
    
		if(!cross2(Li,d[now1]))now1=now1+1==n?0:now1+1;
		if(!cross2(Li,d[now2]))now2=now2+1==n?0:now2+1;
		if(dis(meet(Li,d[now1]),meet(Li,d[now2]))>L)ans++;
		Li.x=Turn(Li.x,0,S,C);Li.y=Turn(Li.y,0,S,C);
	}
	printf("%.4lf",1.0*ans/Times);
}

H. Groundhog Speaking Groundhogish

有一个序列,每个元素都在 1 1 1 ~ m m m 范围内,这个序列可能丢失了至多 k k k 个元素,一个序列的贡献是所有元素的价值之积,求这个序列的所有情况的贡献之和。

对于序列中每两个元素之间,考虑往里面塞元素,为了避免记重,所以不能塞和下一个元素相同的元素,其他元素都可以塞,那么塞一个的所有情况的贡献和就是 v [ i ] = ∑ j ≠ s [ i + 1 ] v a l [ j ] v[i]=\sum_{j\neq s[i+1]}val[j] v[i]=j=s[i+1]val[j] s s s 是原序列, v a l val val 是每个元素的价值。那么塞 k k k 个就是 v [ i ] k v[i]^k v[i]k,于是就转化成了一个完全背包问题。

容易发现这种完全背包问题可以用生成函数优化到 n log ⁡ n n\log n nlogn,令 f ( i ) = 1 1 − v i x f(i)=\dfrac 1 {1-v_ix} f(i)=1vix1,那么 ∏ f ( i ) \prod f(i) f(i) 的前 k + 1 k+1 k+1 项的系数就是答案( x 0 x^0 x0 ~ x k x^k xk),可以先用分治 n t t ntt ntt 求出分母的乘积,再求逆得到 ∏ f ( i ) \prod f(i) f(i)

代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 400010
#define mod 998244353
#define bin(x) (1<<(x))

int n,m,k,a[maxn],val[maxn],v[maxn];
int add(int x){
    
    return x>=mod?x-mod:x;}
int dec(int x){
    
    return x<0?x+mod:x;}
void add(int &x,int y){
    
    x=(x+y>=mod?x+y-mod:x+y);}
void dec(int &x,int y){
    
    x=(x-y<0?x-y+mod:x-y);}
int ksm(int x,int y){
    
    int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int inv[maxn];
struct NTT{
    
    
	int w[maxn];void prep(int lg){
    
    int N=bin(lg);
		inv[1]=1;for(int i=2;i<=N;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
		for(int i=1,wn;i<N;i<<=1){
    
    
			w[i]=1;wn=ksm(3,(mod-1)/(i<<1));
			for(int j=1;j<i;j++)w[i+j]=1ll*w[i+j-1]*wn%mod;
		}
	}
	int limit,r[maxn];
	void work(int lg){
    
    for(int i=1;i<bin(lg);i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));}
	void dft(int *f,int lg,int type=0){
    
    
		limit=bin(lg);if(type)reverse(f+1,f+limit);
		for(int i=1;i<limit;i++)if(i<r[i])swap(f[i],f[r[i]]);
		for(int mid=1,t;mid<limit;mid<<=1)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++)
		{
    
    t=1ll*f[j+i+mid]*w[i+mid]%mod;f[j+i+mid]=dec(f[j+i]-t);f[j+i]=add(f[j+i]+t);}
	}
}ntt;
int A[maxn],B[maxn];
struct POLY{
    
    
	vector<int> a;int len;void rs(int ln){
    
    a.resize(len=ln);}POLY(){
    
    rs(0);}
	int &operator [](int x){
    
    return a[x];}
	void dft(int *A_,int lg,int ln){
    
    for(int i=0;i<bin(lg);i++)A_[i]=i<min(len,ln)?a[i]:0;ntt.dft(A_,lg);}
	void idft(int *A_,int lg,int ln){
    
    ntt.dft(A_,lg,1);rs(ln);for(int i=0;i<ln;i++)a[i]=1ll*A_[i]*inv[bin(lg)]%mod;}
	POLY Mul(POLY b,int ln){
    
    
		int lg=ceil(log2(ln*2-1));ntt.work(lg);dft(A,lg,ln);b.dft(B,lg,ln);
		for(int i=0;i<bin(lg);i++)B[i]=1ll*A[i]*B[i]%mod;b.idft(B,lg,ln);return b;
	}
}F,G;
POLY fenzhi_fft(int l,int r){
    
    
	POLY re;
	if(l==r){
    
    
		re.rs(2);
		re[0]=1;re[1]=mod-v[l];
		return re;
	}
	int mid=l+r>>1;
	re=fenzhi_fft(l,mid);
	POLY p=fenzhi_fft(mid+1,r);
	re=re.Mul(p,r-l+2);return re;
}
void getinv(POLY &f,POLY &g,int ln){
    
    
	if(ln==1){
    
    g.rs(1);g[0]=ksm(f[0],mod-2);return;}getinv(f,g,(ln+1)>>1);
	int lg=ceil(log2(ln*2-1));ntt.work(lg);f.dft(A,lg,ln);g.dft(B,lg,ln);
	for(int i=0;i<bin(lg);i++)B[i]=1ll*B[i]*(2-1ll*A[i]*B[i]%mod+mod)%mod;g.idft(B,lg,ln);
}

int main()
{
    
    
	ntt.prep(18);
	ios::sync_with_stdio(false);
	while(cin>>n>>m>>k,n!=0){
    
    
		for(int i=1;i<=n;i++)cin>>a[i];int sum=0;
		for(int i=1;i<=m;i++)cin>>val[i],add(sum,val[i]);
		a[n+1]=0;for(int i=0;i<=n;i++)v[i]=dec(sum-val[a[i+1]]);
		F=fenzhi_fft(0,n);getinv(F,G,k+1);
		int ans=0;
		for(int i=0;i<=k;i++)add(ans,G[i]);
		for(int i=1;i<=n;i++)ans=1ll*ans*val[a[i]]%mod;
		cout<<ans<<endl;
	}
}

I. The Crime-solving Plan of Groundhog

给出 n n n 个数,在 0 0 0 ~ 9 9 9 范围内,你要将他们组成两个没有前导零的数并使它们乘积最小。

手玩一下或者直接猜,就容易发现拿出最小的一个数,剩下的组成另一个数一定最优。

证明的话考虑一个简单情况: n = 4 n=4 n=4,此时四个数 a , b , c , d a,b,c,d a,b,c,d,满足 a ≤ b ≤ c ≤ d a\leq b\leq c\leq d abcd,有两种组合方法:两位数乘两位数,三位数乘一位数。

根据一个小性质:当 a ≥ b a\geq b ab 时, ( a + 1 ) ( b − 1 ) = a b − a + b − 1 < a b (a+1)(b-1)=ab-a+b-1<ab (a+1)(b1)=aba+b1<ab,并且容易发现 c , d c,d c,d 不可能放在十位数,就能得到最优的两位数乘两位数方式: ( 10 a + c ) ( 10 b + d ) = 100 a b + 10 b c + 10 a d + c d (10a+c)(10b+d)=100ab+10bc+10ad+cd (10a+c)(10b+d)=100ab+10bc+10ad+cd

三位数乘一位数的就比较显然: a × ( 100 b + 10 c + d ) = 100 a b + 10 a c + a d a\times(100b+10c+d)=100ab+10ac+ad a×(100b+10c+d)=100ab+10ac+ad

下面的减上面的得到 10 a c − 10 b c − 9 a d − c d 10ac-10bc-9ad-cd 10ac10bc9adcd,这用脚指头看都知道这个柿子小于 0 0 0,所以三位数乘一位数更优。

n > 4 n>4 n>4 的情况类似,于是就做完了。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010

int T,n,a[maxn];
int ans[maxn],t;

int main()
{
    
    
	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);
		int st=0;while(a[st+1]==0)st++;
		t=0;memset(ans,0,(n+5)<<2);
		for(int i=n;i>=st+3;i--)ans[++t]=a[i];
		for(int i=1;i<=st;i++)ans[++t]=0;
		ans[++t]=a[st+2];
		for(int i=1;i<=t;i++)ans[i]*=a[st+1];
		for(int i=1;i<=t;i++)if(ans[i]>9){
    
    
			ans[i+1]+=ans[i]/10;
			ans[i]%=10;
			if(i==t)t++;
		}
		for(int i=t;i>=1;i--)printf("%d",ans[i]);printf("\n");
	}
}

J. The Escape Plan of Groundhog

给出一个 01 01 01 矩阵,问有多少个子矩阵满足:边界全是 1 1 1,内部 0 , 1 0,1 0,1 数量相差不超过 1 1 1

0 0 0 转成 − 1 -1 1,枚举矩形上下边界,内部用前缀和维护一下就好了,就是看有多少对列 i , j i,j i,j 满足 ∣ s u m [ j − 1 ] − s u m [ i ] ∣ ≤ 1 |sum[j-1]-sum[i]|\leq 1 sum[j1]sum[i]1 s u m [ i ] sum[i] sum[i] 表示上下边界内前 i i i 列所有元素之和,用一个桶维护 s u m [ i ] sum[i] sum[i] 即可。

代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 510
#define off 250000//偏移量,处理负数

int n,m,a[maxn][maxn];
int sum[maxn][maxn];
int occ[maxn*maxn<<1];//桶
vector<int> pos;
long long ans=0;
int getsum(int x,int y,int xx,int yy){
    
    
	if(x>xx||y>yy)return 0;
	return sum[xx][yy]-sum[xx][y-1]-sum[x-1][yy]+sum[x-1][y-1];
}

int main()
{
    
    
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++){
    
    
			scanf("%d",&a[i][j]);if(!a[i][j])a[i][j]=-1;
			sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
		}
	}
	for(int i=1;i<n;i++){
    
    
		for(int j=i+1;j<=n;j++){
    
    
			for(int k=1;k<=m;k++){
    
    
				if(a[i][k]==-1||a[j][k]==-1){
    
    
					for(int p:pos)occ[getsum(i+1,1,j-1,p)+off]--;
					pos.clear();
				}else{
    
    
					if(getsum(i,k,j,k)==j-i+1){
    
    
						int p=getsum(i+1,1,j-1,k-1);
						ans+=occ[p-1+off]+occ[p+off]+occ[p+1+off];
						p=getsum(i+1,1,j-1,k);
						occ[p+off]++;pos.push_back(k);
					}
				}
			}
			for(int p:pos)occ[getsum(i+1,1,j-1,p)+off]--;
			pos.clear();
		}
	}
	printf("%lld",ans);
}

K.The Flee of Groundhog

给出一棵树,给出 A , B A,B A,B 两人初始位置, A A A 的速度是每秒两条边, B B B 的速度是每秒一条边,现在问 A A A 最多要用多少时间才能追到 B B B

一开始以为 B B B 在前 t t t 秒也可以乱走,写完之后发现连样例都过不了,重新读题后发现 B B B 一开始只会往 A A A 走……

A A A 的初始点为根,那么 B B B 肯定往它最深的那个子孙那里走,然后就是个简单的追击问题了。

代码如下:

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,t;
struct edge{
    
    int y,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y){
    
    e[++len]=(edge){
    
    y,first[x]};first[x]=len;}
int fa[maxn],dep[maxn],max_dep[maxn];
void dfs(int x){
    
    
	max_dep[x]=dep[x];
	for(int i=first[x],y;i;i=e[i].next)
	if((y=e[i].y)!=fa[x]){
    
    
		dep[y]=dep[x]+1;fa[y]=x;dfs(y);
		max_dep[x]=max(max_dep[x],max_dep[y]);
	}
}

int main()
{
    
    
	scanf("%d %d",&n,&t);
	for(int i=1,x,y;i<n;i++){
    
    
		scanf("%d %d",&x,&y);
		buildroad(x,y);buildroad(y,x);
	}
	dep[1]=0;fa[1]=0;dfs(1);
	if(dep[n]<=t)return printf("0"),0;
	int pos=n;for(int i=1;i<=dep[n]-t;i++)pos=fa[pos];
	dep[n]=0;fa[n]=0;dfs(n);int ans=0;
	if(max_dep[pos]-dep[pos]>=dep[pos])printf("%d",dep[pos]);
	else printf("%d",(int)ceil((2*dep[pos]-max_dep[pos])/2.0)+max_dep[pos]-dep[pos]);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/108628276
今日推荐