最大疯子树
给定一棵 n 个结点的树,结点编号为 1~n,i 号结点的权重记为 wi(每个点的权值各不相同)。我们定义一个“疯子树”为:
1. 是一个联通子图。
2. 我们将子图内的点按照权重从小到大排序后序列为 b1,b2,…,bm,对于任意的 i(i
输出包含结点最多的“疯子树”的结点数。
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]
一定有$dp[i][1]>dp[i][0]$,$ans=max(dp[i][1])$
时间复杂度和空间复杂度均为$O(n)$。
//Serene
#include
#include
#include
#include
#include
#include
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 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\n",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
#include
#include
#include
#include
#include
#include
蛋糕
今天是鲍勃的生日,爱丽丝打算做一个蛋糕送给他。 这是鲍勃的 n 岁生日,所以爱丽丝的蛋糕必须是正 n 边形。
而且,鲍勃很喜 欢数字 m,所以这个蛋糕必须放在一个正 m 边形的盒子里。
为了让气氛更加浪漫, 爱丽丝将在蛋糕的中心插上一根蜡烛,显然,蜡烛既在蛋糕的中心,又在盒子的 中心是最好的。
换句话说,爱丽丝应该使正 n 边形的蛋糕能被容纳在正 m 边形的盒子里,且 使其中心重合。
事实上,爱丽丝已经做好了蛋糕,蛋糕是边长为 1 的正 n 边形, 现在她想知道,正 m 边形盒子的最小边长是多少。
n,m≤1000000000
题解:
先在n边形外面套一个lcm(n,m)边形,再在lcm(n,m)边形外面套m边形
//Serene
#include
#include
#include
#include
#include
#include
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;
templatevoid 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\n",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
题解:
由于任何时候草的高度的大小关系一定是和a大小关系是相同的
所以对草根据a排序,每次被割的草一定是a比较大的一坨,直接在线段树上二分找到这一坨草割掉就可以了
//Serene
#include
#include
#include
#include
#include
#include
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 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\n",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、考场上做题容易受到干扰,注意力不集中