最大疯子树
给定一棵 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\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<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("\n");
}
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\n");
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\n",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("\n");
}
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\n",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\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<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\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、考场上做题容易受到干扰,注意力不集中