成电体验营

最大疯子树

给定一棵 n 个结点的树,结点编号为 1~n,i 号结点的权重记为 wi(每个点的权值各不相同)。我们定义一个“疯子树”为:

  1. 是一个联通子图。

  2. 我们将子图内的点按照权重从小到大排序后序列为 b1,b2,…,bm,对于任意的 i(i<m),bi到 bi+1最短路径(不含 bi和 bi+1)上的结点的权值都小于等于 i wb 。
输出包含结点最多的“疯子树”的结点数。

n≤200000,0 < wi≤100000000

题解:

简单的树dp。

问题转化:

​ 我们考虑按照权值从小到大把点加入,如果当前这个点$i$可以加入我们之前的一个合法的联通子图$G$,那么$G$一定和$i$有一条边直接联通。

​ 因为$G$里面的点的权值都比i的权值小,所以如果我们把边从无向改为有向,由权值大的点指向权值小的点,那么每个点最多有一个出边,是把这个点加入$G$时和$G$直接相连的那条边,而且后面加入$G$的点如果有向它连的边,那么都是指向它的,是它的入边。

​ 那么问题转化为,给你一个由有向边构成的树,让你找到一个最大的联通子图使得每个点的出度不超过1。

解法:

​ 树dp。随便找个点当做根,$dp[i][0/1]$表示$i$这个点向儿子有$0/1$条出边所能形成的最大联通图。

​ 如果$x$是i的儿子,那么

​ 当$w[x]>w[i]$时,$dp[i][0] += dp[x][0]$ (x连向i)

​ 当$w[x]<w[i]$时,$dp[i][1]=max(dp[x][1]) + dp[i][0]$ (i连向x)

​ 一定有$dp[i][1]>dp[i][0]$,$ans=max(dp[i][1])$

​ 时间复杂度和空间复杂度均为$O(n)$。

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
#define db double
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
const int maxn=2e5+7;
int n,w[maxn],dp[maxn][2],ans;

ll ff;char cc;
template<typename T> void read(T& aa) {
	aa=0; ff=1; cc=getchar();
	while((cc<'0'||cc>'9')&&cc!='-') cc=getchar();
	if(cc=='-') ff=-1,cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	aa*=ff;
}

int fir[maxn],nxt[2*maxn],to[2*maxn],e=0;
void add(int x,int y) {
	to[++e]=y;nxt[e]=fir[x];fir[x]=e;
	to[++e]=x;nxt[e]=fir[y];fir[y]=e;
}

void dfs(int pos,int f) {
	int y,z;
	dp[pos][0]=1; dp[pos][1]=0;
	for(y=fir[pos];y;y=nxt[y]) {
		if((z=to[y])==f) continue;
		dfs(z,pos);
		if(w[z]>w[pos]) dp[pos][0]+=dp[z][0];//pos <- z
		else dp[pos][1]=max(dp[pos][1],dp[z][1]);//pos -> z
	}
	dp[pos][1]+=dp[pos][0];
	ans=max(ans,dp[pos][1]);
}

int main() {
	freopen("crazy.in","r",stdin);
	freopen("crazy.out","w",stdout);
	int x,y;
	while(scanf("%d",&n)==1) {
		For(i,1,n) fir[i]=0; ans=0;
		For(i,1,n) read(w[i]);
		For(i,1,n-1) {
			read(x); read(y);
			add(x,y);
		}
		dfs(1,0);
		printf("%d
",ans);
	}
	fclose(stdin); fclose(stdout);
	return 0;
}

  

分解

给定正整数 N,你需要将其分解为若干正整数的乘积,要求分解出的数之间 相差都不超过 1。

N≤10^18,T≤10

题解:

如果分解成的正整数是x和x+1

Pollard_rho分解,对于一个质数p_i,如果N中含有a_i个p_i,那么要么x^r包含a_i个p_i,要么(x+1)^t包含,状压枚举x中包含哪些质数

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<map>
using namespace std;
#define ll long long
#define db double
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
const int TM=25,maxn=1007;
const ll INF=2e18;
ll Td,tot,ans;
map<ll,ll> G;
map<ll,ll>::iterator it;
ll zz[maxn],num[maxn];

ll ff;char cc;
template<typename T> void read(T& aa) {
	aa=0; ff=1; cc=getchar();
	while((cc<'0'||cc>'9')&&cc!='-') cc=getchar();
	if(cc=='-') ff=-1,cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	aa*=ff;
}

ll gcd(ll x,ll y) {
	return y==0? x:gcd(y,x%y);
}

ll qc(ll x,ll k,ll mod) {
	ll rs=0;
	while(k) {
		if(k&1) rs=(rs+x)%mod;
		k>>=1; x=(x+x)%mod;
	}
	return rs;
}

ll qp(ll x,ll k,ll mod) {
	ll rs=1;
	while(k) {
		if(k&1) rs=qc(rs,x,mod);
		k>>=1; x=qc(x,x,mod);
	}
	return rs;
}

bool isprime(ll n) {
	if(n==2||n==3||n==5||n==7) return 1;
	if(n<2||(n%6!=1&&n%6!=5)) return 0;
	ll m=n-1,t=0,x,y;
	while((m&1)==0) t++,m>>=1;
	For(qaq,1,TM) {
		x=rand()%(n-2)+2;
		x=qp(x,m,n);
		For(i,1,t) {
			y=qc(x,x,n);
			if(y==1&&x!=1&&x!=n-1) return 0;
			x=y;
		}
		if(y!=1) return 0;
	}
	return 1;
}

ll prho(ll n,ll c) {
	ll x=rand()*rand()%n+1,y=x,k=2,i=0,d;
	while(1) {
		x=(qc(x,x,n)+c)%n+2;
		d=gcd((y-x+n)%n,n);
		if(d>1&&d<n) return d;
		if(x==y) return n;
		if((++i)==k) {
			k<<=1; y=x;
		}
	}
	return n;
}

void find(ll n,ll m) {
	if(isprime(n)) {
		++G[n];
		return;
	}
	ll k=m,p=n;
	while(p>=n&&m) p=prho(n,m--);
	find(p,k); find(n/p,k);
}


struct Node{
	ll x,num1,num2;
	Node(){}
	Node(ll x,ll num1,ll num2):x(x),num1(num1),num2(num2){}
	bool operator < (const Node& b) const{return x<b.x;}
}node[maxn*maxn];

ll p[maxn];
void get_ans(ll s) {
	ll gcd1=0,gcd2=0;
	For(i,1,tot) 
		if((s>>(i-1))&1){
			if(!gcd1) gcd1=num[i];
			else gcd1=gcd(gcd1,num[i]);
		}
		else {
			if(!gcd2) gcd2=num[i];
			else gcd2=gcd(gcd2,num[i]);
		}
	ll x=1,y=1,num1=1,num2=1,o=0;
	For(i,1,tot) if((s>>(i-1))&1) num1*=qp(zz[i],num[i]/gcd1,INF);
	For(i,1,tot) if((s>>(i-1)&1)==0) num2*=qp(zz[i],num[i]/gcd2,INF);
	for(it=G.begin();it!=G.end();++it) {
		p[++o]=it->first;
		num[o]=it->second;
	}
	For(r,1,gcd1) if(gcd1%r==0) {
		x=qp(num1,gcd1/r,INF);
		For(t,1,gcd2) if(gcd2%t==0) {
			y=qp(num2,gcd2/t,INF);
			if(y!=x+1) continue;
			if(y<x) break;
			node[++ans]=Node(x,r,t);
		}
	}
}

void print_ans(int p) {
	printf("%lld ",node[p].num1+node[p].num2);
	For(i,1,node[p].num1) printf("%lld ",node[p].x);
	For(i,1,node[p].num2) printf("%lld ",node[p].x+1);
	printf("
");
}

int main() {
	freopen("little.in","r",stdin);
	freopen("little.out","w",stdout);
	ll n,sz,x; read(Td);
	while(Td--) {
		read(n);
		if(n==(n&-n)) {
			printf("-1
");
			continue;
		}
		G.clear(); tot=ans=0;
		find(n,120);
		for(it=G.begin();it!=G.end();++it) {
			zz[++tot]=it->first;
			num[tot]=it->second;
		}
		if(tot<2) {
			For(i,1,num[1]) if(num[1]%i==0) ++ans;
			printf("%lld
",ans);
			For(i,1,num[1]) if(num[1]%i==0) {
				x=qp(zz[1],i,INF);
				printf("%lld ",num[1]/i);
				For(j,1,num[1]/i) printf("%lld ",x);
				printf("
");
			}
			continue;
		}
		sz=1<<tot;
		ll gcd1=num[1];
		For(i,1,tot) gcd1=gcd(gcd1,num[i]);
		For(r,1,gcd1) if(gcd1%r==0) {
			x=1;
			For(i,1,tot) x*=qp(zz[i],num[i]/r,INF);
			node[++ans]=Node(x,r,0);
		}
		for(int s=1;s<sz-1;++s) get_ans(s);
		printf("%lld
",ans);
		sort(node+1,node+ans+1);
		For(i,1,ans) print_ans(i);
	}
	fclose(stdin); fclose(stdout);
	return 0;
}

  

蛋糕

今天是鲍勃的生日,爱丽丝打算做一个蛋糕送给他。 这是鲍勃的 n 岁生日,所以爱丽丝的蛋糕必须是正 n 边形。

而且,鲍勃很喜 欢数字 m,所以这个蛋糕必须放在一个正 m 边形的盒子里。

为了让气氛更加浪漫, 爱丽丝将在蛋糕的中心插上一根蜡烛,显然,蜡烛既在蛋糕的中心,又在盒子的 中心是最好的。

换句话说,爱丽丝应该使正 n 边形的蛋糕能被容纳在正 m 边形的盒子里,且 使其中心重合。

事实上,爱丽丝已经做好了蛋糕,蛋糕是边长为 1 的正 n 边形, 现在她想知道,正 m 边形盒子的最小边长是多少。

n,m≤1000000000

题解:

先在n边形外面套一个lcm(n,m)边形,再在lcm(n,m)边形外面套m边形

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
#define db long double
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
const db PI=acos(-1);
db n,m,p;

char cc; ll ff;
template<typename T>void read(T& aa) {
	aa=0;cc=getchar();ff=1;
	while((cc<'0'||cc>'9')&&cc!='-') cc=getchar();
	if(cc=='-') ff=-1,cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	aa*=ff;
}

ll gcd(ll x,ll y) {return y==0? x:gcd(y,x%y);}

int main() {
	freopen("cake.in","r",stdin);
	freopen("cake.out","w",stdout);
	while(scanf("%Lf%Lf",&n,&m)==2) {
		p=n/gcd(n,m)*m;
		printf("%.4Lf
",cos(PI/p)*tan(PI/m)/sin(PI/n));
	}
	return 0;
}

  

Chika 的烦恼

Chika 家经营着一家旅馆,她经常要帮家里的忙,有时候,她需要去清理长 满杂草的后院。

具体地,后院里面的每棵草都有其生长速率(每天所能生长的高 度),在某些给定的日期,她需要把高度大于某个值 b 的杂草割掉一截,使其高 度剩余 b。

(b 在每次给定的日期时不一定相同。)

她想请你帮她统计一下,每次她所割掉的草的总量是多少。

假设最开始每棵草的高度是零。

1≤n,m≤400000, 1≤ai≤10^6 , 1≤di≤10^12 ,0≤bi≤10^12,d1<d2<...<dm,保证在每次割草时,没有草的高度会超过 2×10^12

题解:

由于任何时候草的高度的大小关系一定是和a大小关系是相同的

所以对草根据a排序,每次被割的草一定是a比较大的一坨,直接在线段树上二分找到这一坨草割掉就可以了

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
#define db double
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
const int maxn=4e5+7;
ll n,m,a[maxn],b[maxn];

ll ff;char cc;
template<typename T> void read(T& aa) {
	aa=0; ff=1; cc=getchar();
	while((cc<'0'||cc>'9')&&cc!='-') cc=getchar();
	if(cc=='-') ff=-1,cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	aa*=ff;
}

bool cmp(const ll x,const ll y) {
	return x>y;
}

ll sum[4*maxn],maxnum[4*maxn],laz[4*maxn],laz2[4*maxn],ql,qr,qx;

int pd(int pos,ll l,ll r) {
	ll mid=(l+r)>>1;
	if((laz[pos]==-1&&laz2[pos]==0)||l==r) {
		laz[pos]=-1; laz2[pos]=0;
		return mid;
	}
	if(laz[pos]!=-1) {
		laz[pos<<1]=laz[pos<<1|1]=laz[pos];
		laz2[pos<<1]=laz2[pos<<1|1]=0;
		sum[pos<<1]=(mid-l+1)*laz[pos];
		sum[pos<<1|1]=(r-mid)*laz[pos];
		maxnum[pos<<1]=maxnum[pos<<1|1]=laz[pos];//
	}
	if(laz2[pos]) {
		laz2[pos<<1]+=laz2[pos];
		laz2[pos<<1|1]+=laz2[pos];
		sum[pos<<1]+=(b[mid]-b[l-1])*laz2[pos];
		sum[pos<<1|1]+=(b[r]-b[mid])*laz2[pos];
		maxnum[pos<<1]+=laz2[pos]*a[l];//
		maxnum[pos<<1|1]+=laz2[pos]*a[mid+1];//
	}
	laz[pos]=-1; laz2[pos]=0;
	return mid;
}

void chge(int pos,ll l,ll r) {
	int mid=pd(pos,l,r);
	if(l>=ql&&r<=qr) {
		laz[pos]=maxnum[pos]=qx;
		laz2[pos]=0;
		sum[pos]=(r-l+1)*qx;
		return ;
	}
	if(ql<=mid) chge(pos<<1,l,mid);
	if(qr>mid) chge(pos<<1|1,mid+1,r);
	sum[pos]=sum[pos<<1]+sum[pos<<1|1];
	maxnum[pos]=max(maxnum[pos<<1],maxnum[pos<<1|1]);
}

ll q(int pos,int l,int r) {
	int mid=pd(pos,l,r);
	if(l>=ql&&r<=qr) return sum[pos];
	ll rs=0;
	if(ql<=mid) rs+=q(pos<<1,l,mid);
	if(qr>mid) rs+=q(pos<<1|1,mid+1,r);
	return rs;
}

ll get_ans(int pos,int l,int r,ll x) {
	int mid=pd(pos,l,r);
	if(l==r) return l;
	if(maxnum[pos<<1|1]>x) return get_ans(pos<<1|1,mid+1,r,x);
	else return get_ans(pos<<1,l,mid,x);
}

int ef(ll x) {
	ql=qr=1; if(q(1,1,n)<=x) return 0;
	ql=qr=n; if(q(1,1,n)>x) return n;
	return get_ans(1,1,n,x);
}

int main() {
	freopen("grass.in","r",stdin);
	freopen("grass.out","w",stdout);
	read(n); read(m);
	memset(laz,-1,sizeof(laz));
	For(i,1,n) read(a[i]);
	sort(a+1,a+n+1,cmp);
	For(i,1,n) b[i]=b[i-1]+a[i];
	ll x,y,pos,last=0,tot;
	For(i,1,m) {
		read(x); read(y);
		qx=x-last;
		pd(1,1,n);
		sum[1]+=qx*b[n];
		laz2[1]+=qx; maxnum[1]+=qx*a[1];
		tot=sum[1];
		if((pos=ef(y))!=0) {
			ql=1; qr=pos; qx=y;
			chge(1,1,n);
		}
		printf("%lld
",tot-sum[1]);
		last=x;
	}
	fclose(stdin); fclose(stdout);
	return 0;
}
/*
4 4
1 2 4 3
1 1
2 2
3 0
4 4
*/

  

总结:

考场上真想骂自己是个sb。

t2,Pollard_rho板子写错了总是调不出来,最后写了一个暴力的分解大数。

t3以前饺学长讲过,还有点印象,记得和什么gcd和lcm有关,但是还是做不出来爆零了。

弃了t3之后,在最后一个小时才开始做t4。t1和t2对拍的时间都没有。

考完了觉得自己确实是个sb。

t4一个这么无聊的线段树,竟然认为先二分再在线段树里面查,两个log能过,结果被卡成60。

t2自己作死把输出的格式写错了,暴力分都没拿到,成功挂成10分。

这么容易AK的一套题竟然连暴力分都没拿够。

主要是这几个问题:

1、板子不够熟练

2、数据结构没有用熟

3、数学和计算几何辣鸡

4、考场上做题容易受到干扰,注意力不集中

原文地址:https://www.cnblogs.com/Serene-shixinyi/p/8611754.html