M
a
r
1
s
t
Mar_{1st}
Mar1st
T
1
T1
T1
分数不好思考,考虑成二元组。
两种操作
①
(
a
,
b
)
→
(
a
+
b
,
b
)
②
(
a
,
b
)
→
(
−
b
,
a
)
①(a,b)\rightarrow (a+b,b)\\②(a,b)\rightarrow (-b,a)
①(a,b)→(a+b,b)②(a,b)→(−b,a)
希望最后将
a
a
a变成
0
0
0.
先手玩。
得到一种方案:
如果分数是负的,执行一操作,否则执行二操作。
这个证明待消化。
因此考虑一种方式(这里强制b>0):
a如果是正的,O(1)将(a,b)变化成(-b,a)。
否则对于(a,b)变成(b - (-a)%b,b)。
考试的时候这样得了50分。
复杂度显然不对。考虑负数a的绝对值太小。
注:
考试的时候发现了这一点,因为将过程打表出来很有规律,它是逐步呈阶数递减的。
然后错了。
发现(a,b)变成(b - (-a)%b,b)并不是一步。
感觉很像辗转相除,因此朝向取模靠。
待消化部分:
在b<-a<2b时,进行3部实现对(-a-b)取模
(a,b)->(a+b,b)->(a+2b,b)->(-b,a+2b)=(a+(-a-b),b-(-a-b))
好像复杂度就是log了。
这道题还需要将辗转这一实质想通。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll gcd(ll x,ll y)
{if(y==0) return x; return gcd(y,x%y);}
ll solve(ll a,ll b)
{
if(a==0) return 0;
if(a>0) return solve(-b,a)+1;
if(b<-a&&-a<2*b)
{
ll t=(b-1)/(-(a+b));
return solve(a+t*(-a-b),b-t*(-a-b))+3*t;
}
ll t=-(a+1)/b+1;
return solve(a+t*b,b)+t;
}
int main()
{
ll t,a,b; cin>>t;
while(t--)
{
scanf("%lld%lld",&a,&b);
ll g=gcd(a,b); a/=g; b/=g;
if(b<0) a=-a,b=-b;
printf("%lld\n",solve(a,b));
}
return 0;
}
T
2
T2
T2
考试一直在想容斥,dp。
停fsy说对树计数也需要想到矩阵树定理。
感觉这题挺妙的。
思路待消化
考虑构造一个完全有向图。
i向j连边。
如果i的标号比j小连x条边,这个x是形式幂。
如果i的标号比j大连1条边。
这样以r为根的外向树的数量就是一个关于x的多项式。
考虑
x
m
x^m
xm的系数就是答案。
仍然不能暴力乘。因为行列式中不是数,是多项式。直接乘会炸的。
本题学到:多个多项式相乘考虑分治。
主对角线先分治求了。
考虑其它的。
从逆序对的那种定义的角度来考虑这个行列式。
考虑方式:每行每列必须选择有且仅有一个,选出来的n个数相乘配上奇偶排列系数即可。
考虑枚举2到n-1(已去掉r行r列)行。
当前在第i行。
选择第i行的-1,那么第i行其它的都不能选了,但是第i列必须选,这列只剩一个数,就是主对角线上面那个,接着第i-1行又这样类似处理,向下也是类似处理。
最后发现枚举取每行的-1,最后都是这样乘起来。
对主对角线上面哪里,从左上到右下做前缀积记作s,主对角线从右下到左上做后缀积记作S。
答案就模糊记作
∏
2
≤
i
≤
n
−
1
s
i
−
1
S
i
+
1
\prod\limits_{2\le i\le n-1}s_{i-1}S_{i+1}
2≤i≤n−1∏si−1Si+1
之所以说模糊记作是还没有考虑那个-1和逆序对数(i-1个)带来的-1的系数,但这是细节,不是现在考虑的重点。
但是仍然不能暴力算s,S。
分治也困难,因为要对每个位置都要算。
其实还是可以分治的。
不是算s,S,而是直接计算答案。
上式中的i可以形象的理解成“断点”。
计算的范围是2到n-1.
考虑计算[l,r]的范围的答案,s,S。
解释:
[l,r]的答案就是
∏
l
≤
i
≤
r
s
i
−
1
S
i
+
1
(
这
里
s
,
S
也
是
只
到
l
,
r
这
两
个
边
界
)
\prod\limits_{l\le i\le r}s_{i-1}S_{i+1}(这里s,S也是只到l,r这两个边界)
l≤i≤r∏si−1Si+1(这里s,S也是只到l,r这两个边界)
s就是[l,r]范围内的前缀积
S就是[l,r]范围内的后缀积
分治过程:
计算出[l,mid] [mid+1,r]后
s和S很好转移,直接乘起来即可。
答案就是左边s×右边的答案×
−
1
m
i
d
−
l
+
1
-1^{mid-l+1}
−1mid−l+1+左边的答案×右边的S。
上面那个-1系数是处理逆序对,指数配成l而没搞成mid-l+1调了很久.
时间复杂度
n
l
o
g
2
n
nlog^2n
nlog2n
然后我代码中有很多细节,巨大常数导致(7个ntt)我只有70.
pmh说细节很少(并且他只用了5个ntt),fsy现场A。待优化细节。
#include<bits/stdc++.h>
using namespace std;
int const mod=998244353,G=3,i2=499122177,maxl=1<<18;
inline int Inc(int a,int b) {return (a+b)%mod;}
inline int Dec(int a,int b) {return ((a-b)%mod+mod)%mod;}
inline int Mul(int a,int b) {return 1ll*a*b%mod;}
inline int qpow(int a,int b)
{int ret=1; for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ret=1ll*ret*a%mod; return ret;}
inline void inc(int &a,int b) {a=Inc(a,b);}
inline void dec(int &a,int b) {a=Dec(a,b);}
inline void mul(int &a,int b) {a=Mul(a,b);}
#define rs resize
#define be begin()
#define en end()
typedef vector<int> pl;
namespace poly
{
int rev[maxl];
inline void init(int len) {for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)?len>>1:0);}
inline void ntt(pl &a,int len,int flag=1)
{
for(register int i=0;i<len;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(register int h=2;h<=len;h<<=1)
{
int wn=qpow(G,(mod-1)/h); if(flag==-1) wn=qpow(wn,mod-2);
for(register int j=0;j<len;j+=h)
{
int w=1;
for(register int k=j;k<j+h/2;w=Mul(w,wn),k++)
{int p=a[k],q=Mul(w,a[k+h/2]); a[k]=Inc(p,q); a[k+h/2]=Dec(p,q);}
}
}
if(flag==-1) {int len_=qpow(len,mod-2); for(register int i=0;i<len;i++) a[i]=Mul(a[i],len_);}
}
inline int calc_len(int n)
{int len=1; while(len<n) len<<=1; init(len); return len;}
inline pl NEG(pl a) {for(int i=0;i<a.size();i++) a[i]=Dec(0,a[i]); return a;}
inline pl INC(pl a,pl b,int opt=0)
{
int m=a.size(),n=b.size();
int len=max(m,n);
a.rs(len); b.rs(len);
if(opt==0)
for(register int i=0;i<len;i++)
a[i]=Inc(a[i],b[i]);
else
for(register int i=0;i<len;i++)
a[i]=Dec(a[i],b[i]);
return a;
}
inline pl MUL(pl a,pl b)
{
int m=a.size(),n=b.size();
int len=calc_len(m+n-1);
a.rs(len); b.rs(len);
ntt(a,len); ntt(b,len);
for(register int i=0;i<len;i++)
a[i]=Mul(a[i],b[i]);
ntt(a,len,-1); a.rs(m+n-1); return a;
}
}using namespace poly;
int const maxn=1e5+5;
int n,r,m;
pl a[maxn],b[maxn];//a 主对角线 b主对角线上面
pl solve_1(int l,int r)//主对角线
{
if(l==r) return a[l];
int mid=l+r>>1;
pl L=solve_1(l,mid);
pl R=solve_1(mid+1,r);
return MUL(L,R);
}
#define fi first
#define se second
struct tmp{pl ans,s,S;};
/*
去掉b1
[l,r]的答案是
以i为断点 s[i]*S[i+1],i可以取l,r。
*/
tmp solve_2(int l,int r)
{
if(l==r) return (tmp){INC(a[l+2],b[l+1],1),b[l+1],a[l+2]};
int mid=l+r>>1;
tmp L=solve_2(l,mid),R=solve_2(mid+1,r);
pl ret=INC(MUL(L.ans,R.S),MUL(L.s,R.ans),(mid-l+1)&1);
ret=INC(ret,MUL(L.s,R.S),!((mid-l+1)&1));//配了个小容斥,因为L.s*R.S会算重
return (tmp){ret,MUL(L.s,R.s),MUL(L.S,R.S)};
}
int main()
{
cin>>n>>r>>m;
if(n==1) {cout<<1<<endl; return 0;}
if(n==2) {cout<<(r==1&&m==0||r==2&&m==1)<<endl; return 0;}
for(int i=1;i<n;i++)
a[i].rs(2),
a[i][0]=i<r?i-1:i,
a[i][1]=i<r?n-i:n-i-1;
for(int i=1;i<n-1;i++)
b[i]=NEG(a[i]),
dec(b[i][1],1);
for(int i=2;i<n;i++)
inc(a[i][0],1);
pl ans1=solve_1(1,n-1);
pl ans2=n>3?MUL(solve_2(1,n-3).ans,b[1]):b[1];
pl ans=INC(ans1,ans2);
cout<<ans[m]<<endl;
return 0;
}
T
3
T3
T3
考虑三个图
T,L(T),L(L(T))
T这是原图,一棵树。
L(T) 是一个团状树。一个点的度数是多少就对应几元团
L(L(T))就没有什么特点了。
注意到要求L(L(T))上两个点之间的最短路。
考虑L(L(T))的点是什么。
L(L(T))上的一个点相当于L(T)上的一条边同时相当于T上的一个三点链。
考虑在L(L(T))上从一个点走到另一个相邻的点是什么。
在L(T)上相当于从一条边走到相邻的一条边。
在T上相当于从一条三点链走到与它相邻的三点链。(这里定义两个三点链相邻:它们有且仅有一条公共边。),那么从一条三点链走到与它相邻的三点链可以看做删掉这个三点链的一条边,加入一条新边。
考虑三点链走到与它相邻的三点链的代价是什么。它相当于在T上把一条三点链删掉一条边,加入一条新边。不难发现代价是删掉的边权和加入的边权加上不变的边权的两倍。
那么现在清晰知道在L(L(T))走一条边如何对于到原图T了,考虑答案就是T上所有三点链对之间最短路之和。
现在考虑T上一对三点链之间的最短路。
由于T是树,因此这个最短路是可以方便地构造出来而不是跑最短路算法。
开始分类讨论
一、两个不相交
定义两个链的树上链为它们之间距离最近的两个点之间的路径。
观察如何走最短路,如图分析:
要从上面的红色走到下面的褐色
此时红色先选择是从浅蓝走到咖啡更好(显然如果红色与谁相交的边的边权更小就选谁),还是深蓝走到咖啡更好。然后顺着咖啡走橙走紫然后走到褐色。
根据之前总结的两个三点链之间如何挪移。
因此出结论:最短路是两个链的树上链的长度乘4在加上两个三点链做的贡献:
如果这个三点链是用链心去迎接两个链的树上链(就像图中的红色),贡献它的较大的边权加较小的边权乘3.
如果这个三点链是用端点去迎接两个链的树上链(就像图中的褐色),贡献那个去迎接的边的大小的两倍加上后面的边的大小
二、两个相交
三种情况:X型,Y型,Z型:
根据就自己挪一挪三点链就可以了。
然后情况分清楚了考虑如何计算,因为我们不能枚举所有三点链。
考虑这些情况的值都是得到了边对它们的贡献。
我们对一些情况直接计算它们的到了边给它们的多少贡献。
然后计算每条边对其他所有情况做的贡献。
代码中有讲解,注释在后面。
#include<bits/stdc++.h>
using namespace std;
#define int long long //好像这个代码关于取模会溢出,不想去调试了,偷个懒
inline int rd()
{
int v=0; char c=getchar(); bool flag=false;
while(c<'0'||c>'9') flag|=(c=='-'),c=getchar();
while('0'<=c&&c<='9') v=(v<<1)+(v<<3)+(c^48),c=getchar();
return flag?-v:v;
}
int const maxn=5e5+5,mod=998244353;
inline int Inc(int x,int y) {return (x+y)%mod;}
inline int Dec(int x,int y) {return ((x-y)%mod+mod)%mod;}
inline void inc(int &x,int y) {x=Inc(x,y);}
int n; int de[maxn];
struct graph{int v,w,nxt;}e[maxn<<1]; int fst[maxn]; int tot=1;
inline int addedge(int from,int to,int far)
{e[++tot]=(graph){to,far,fst[from]}; fst[from]=tot; de[from]++;}
int f[maxn],g[maxn];
void dfs(int u,int ff)
{
f[u]=1ll*de[u]*(de[u]-1)/2%mod;
for(int v,k=fst[u];k;k=e[k].nxt)
if((v=e[k].v)!=ff)
dfs(v,u),
inc(f[u],f[v]);
}
int to[maxn],val[maxn],Sw[maxn];
int su[maxn],sv[maxn];
int pre[maxn],suf[maxn];
inline bool cmp(int i,int j) {return val[i]<val[j];}
int ans;
void work(int u,int ff)
{
for(int k=fst[u];k;k=e[k].nxt)
if(e[k].v!=ff)
work(e[k].v,u);
int tot=0;
for(int v,k=fst[u];k;k=e[k].nxt)
to[++tot]=v=e[k].v,
val[to[tot]]=e[k].w,
inc(Sw[u],e[k].w),
su[v]=v==ff?f[u]:g[v],//考虑u-v这条边把树分成了两棵子树,su[v]表示u这一侧的三点链,sv[v]表示v这一侧的三点链
sv[v]=v==ff?g[u]:f[v];
sort(to+1,to+tot+1,cmp);
pre[0]=suf[tot+1]=0;
for(int i=1;i<=tot;i++) pre[i]=Inc(pre[i-1],sv[to[i]]);
for(int i=tot;i;i--) suf[i]=Inc(suf[i+1],sv[to[i]]);
//核心代码
for(int i=1;i<=tot;i++)//求边对一些情况做的贡献
{
int v=to[i],du=de[u],dv=de[v]; long long w=val[v];
inc(ans,1ll*(tot-i)*(tot-i-1)*(tot-i-2)/6*9%mod*w%mod);//以u为中心X字形,当前边为第1小边,注1。
inc(ans,1ll*(i-1)*(tot-i)*(tot-i-1)/2*7%mod*w%mod);//以u为中心X字形,当前边为第2小边
inc(ans,1ll*(i-1)*(i-2)/2*(tot-i)*5%mod*w%mod);//以u为中心X字形,当前边为第3小边
inc(ans,1ll*(i-1)*(i-2)*(i-3)/6*3%mod*w%mod);//以u为中心X字形,当前边为第4小边
inc(ans,1ll*(tot-1)*(tot-2)/2*4%mod*w%mod);//以u为中心的Y字形,注2
if(v!=ff) inc(ans,1ll*(sv[v]-(dv-1))*(su[v]-(du-1))*4%mod*w%mod);//这条边作为中间边对两种情况的贡献,注意if,注3
//考虑这条边对以u为中点且u-v是较小边的用中点去迎接别的三点链的三点链做的贡献
inc(ans,1ll*Dec(su[v],1ll*du*(du-1)/2%mod)*(tot-2)%mod*w%mod);//先把它自身的大小加上,然后再处理较小的边的两倍即可。
//下面处理较小的边的两倍做出的贡献。这里可能处理复杂了,可以先把当前边选择一个更大的边,然后再任选一条边连出去就行了
//这里考虑成了先选择一条边连出去,因此就对当前边能选择的更大的边的数量产生影响,就加了分类讨论的细节
//没想通的时候参考别人的,现在不想改了
inc(ans,1ll*Dec(su[v],pre[i-1]+1ll*du*(du-1)/2%mod)*(tot-i-1)%mod*2*w%mod);//中点连出,当前边最小,且中点连出边比当前边大
inc(ans,1ll*Dec(su[v],suf[i+1]+1ll*du*(du-1)/2%mod)*(tot-i)%mod*2*w%mod);//中点连出,当前边最小,且中点连出边比当前边小
}
for(int i=1;i<=tot;i++)//求一些情况得到边的贡献
{
int v=to[i],w=val[v],du=de[u],dv=de[v];
int tmp1=ans;
if(v!=ff) inc(ans,1ll*(du-1)*(dv-1)*2%mod*w%mod+Inc(1ll*(Sw[u]-w)*(dv-1)%mod,1ll*(Sw[v]-w)*(du-1)%mod));//以当前边为中心边的Z型相交所得到的边的贡献
inc(ans,(1ll*(tot-1)*3*w%mod+1ll*(Sw[u]-w))*(sv[v]-(dv-1))%mod);//以u为中心向外用端点v去迎接别的三点链的三点链所得到的边的贡献
}
}
signed main()
{
n=rd();
for(int u,v,w,i=1;i<n;i++)
u=rd(),v=rd(),w=rd(),
addedge(u,v,w),addedge(v,u,w);
dfs(1,0);
for(int i=1;i<=n;g[i]=f[1]-f[i],i++);
work(1,0);
cout<<Dec(ans,0)<<endl;
return 0;
}
注1:
注2:
注3:解释为什么会有这个if,而前面其他情况不需要。
因为如果不加if,在u这里会处理这个贡献,v也会处理。
换句话说,这条边对u和对v呈对称关系。以u为端点的边和以v为端点的边不能完全说是两码事。
而考虑其他的,例如Y那种情况。
这条边只对以u为中心的Y贡献,而以u为中心的Y和以v为中心的Y是两码事。
M
a
r
2
n
d
Mar_{2nd}
Mar2nd
T
1
T1
T1
还需要梳理清楚dfs树的性质,练习权值线段树
T
2
T2
T2
T
3
T3
T3
M
a
r
3
r
d
Mar_{3rd}
Mar3rd
T
1
T1
T1
T
2
T2
T2
T
3
T3
T3
自环很好处理。
然后处理选中的点向其他点连一条边,也很好处理。
最后就是把选中的点作为根,然后把不在同一棵子树随意连边。
只用处理最后一个问题。
就是计算所有子树大小两两之积之和。
套路是化成二分之一倍和的平方减平方和。
问题转化为求子树的和与平方和,子树和很好用dfs序处理,但是处理平方和的话暴力枚举是
n
2
n^2
n2的。
现在只用考如何处理子树的平方和。
考试的时候用树链剖分暴力维护
n
l
o
g
2
n
nlog^2n
nlog2n有75分。
然后下来用lct维护子树信息复杂度
n
l
o
g
n
nlogn
nlogn常数太大60分。
#include<bits/stdc++.h>
using namespace std;
inline int rd()
{
int v=0; char c=getchar(); bool flag=false;
while(c<'0'||c>'9') flag|=(c=='-'),c=getchar();
while('0'<=c&&c<='9') v=(v<<1)+(v<<3)+(c^48),c=getchar();
return flag?-v:v;
}
int const maxn=5e5+5,mod=998244353,inv2=(mod+1)>>1;
inline int Inc(int x,int y) {return (x+y)%mod;}
inline int Dec(int x,int y) {return ((x-y)%mod+mod)%mod;}
inline int Mul(int x,int y) {return 1ll*x*y%mod;}
inline int Sq(int x) {return 1ll*x*x%mod;}
inline void inc(int &x,int y) {x=Inc(x,y);}
inline void dec(int &x,int y) {x=Dec(x,y);}
struct graph{int v,nxt;}e[maxn<<1]; int fst[maxn]; int tot=1;
int n,m;
namespace LCT
{
#define lc ch[0]
#define rc ch[1]
struct node
{
node *fa,*ch[2];
int a,sz,sz_1,sz_2; bool r;
inline void cpy(node *f,node *ls,node *rs) {fa=f;lc=ls;rc=rs;}
}*nul,*t[maxn];
bool which(node *x) {return x->fa->rc==x;}
bool isroot(node *x) {return x->fa->lc!=x&&x->fa->rc!=x;}
void pd(node *x)
{
if(x->r)
{
swap(x->lc,x->rc);
if(x->lc!=nul) x->lc->r^=1;
if(x->rc!=nul) x->rc->r^=1;
x->r=false;
}
}
void pu(node *x)
{
pd(x->lc); pd(x->rc);
x->sz=Inc(Inc(x->lc->sz,x->rc->sz),Inc(x->sz_1,x->a));
}
void lnk(node *x,node *f,bool ws)
{if(f!=nul) f->ch[ws]=x; if(x!=nul) x->fa=f; pu(f);}
void rot(node *x)
{
node *f=x->fa,*g=f->fa;
bool xw=which(x),fw=which(f),flag=isroot(f);
lnk(x->ch[xw^1],f,xw); lnk(f,x,xw^1);
if(!flag) lnk(x,g,fw); else x->fa=g;
}
void pushpath(node *x) {if(!isroot(x)) pushpath(x->fa); pd(x);}
void splay(node *x)
{
if(x==nul) return ; pushpath(x);
for(;!isroot(x);rot(x))
if(!isroot(x->fa))
rot(which(x)==which(x->fa)?x->fa:x);
}
void acs(node *x)
{
for(node *y=nul;x!=nul;y=x,x=x->fa)
splay(x),
inc(x->sz_1,Dec(x->rc->sz,y->sz)),
inc(x->sz_2,Dec(Sq(x->rc->sz),Sq(y->sz))),
x->rc=y,pu(x);
}
void mrt(node *x)
{acs(x); splay(x); x->r^=1;}
void lnk(node *x,node *y)
{
mrt(x); acs(y); splay(y);
inc(y->sz_1,x->sz); inc(y->sz_2,Sq(x->sz));
x->fa=y; pu(y);
}
void init()
{
nul=new node(); nul->cpy(nul,nul,nul);
for(int i=1;i<=n;i++) t[i]=new node(),t[i]->cpy(nul,nul,nul);
}
}using namespace LCT;
inline int solve(int x)
{
mrt(t[x]);
int ans1=Sq(Dec(t[x]->sz,t[x]->a));
int ans2=Inc(t[x]->sz_2,Inc(Sq(t[x]->lc->sz),Sq(t[x]->rc->sz)));
int ans=Mul(Dec(ans1,ans2),inv2);
inc(ans,Mul(Mul(t[x]->a,t[x]->a-1),inv2));
inc(ans,Mul(t[x]->a,Dec(t[x]->sz,t[x]->a)));
return ans;
}
inline void grow(int x,int xa)
{mrt(t[x]); inc(t[x]->a,xa); pu(t[x]);}
int main()
{
n=rd(); m=rd(); init();
for(int i=1;i<=n;grow(i,rd()),i++);
for(int i=1;i<n;lnk(t[rd()],t[rd()]),i++);
for(int i=1;i<=m;i++)
{
int opt=rd();
if(opt^1) printf("%d\n",solve(rd()));
else
{
int x=rd(),xa=rd();
grow(x,xa);
}
}
return 0;
}
考虑lct维护子树是采用虚子树。
可以采用树剖也用维护虚子树的手段。
每个点维护虚子树平方和。
查询就是虚子树平方和再加上查询这棵重子树的大小的log,以及查询整棵树的log,这样n-整棵树的大小就是上面那个子树。
考虑修改。一个点增加了x,它到根只经过log条轻边。对每条轻边产生的贡献是
x
2
+
2
x
∗
s
i
z
e
重
子
树
大
小
x^2+2x*size_{重子树大小}
x2+2x∗size重子树大小。但是我们不能查询这个重子树大小,因此需要记录所有链的链顶这个点的子树大小,而这在修改时是很好维护的。
代码待编
M
a
r
.
6
t
h
Mar._{6th}
Mar.6th
T
1
T1
T1
T
2
T2
T2
T
3
T3
T3
M
a
r
.
8
t
h
Mar._{8th}
Mar.8th
T
1
T1
T1
T
2
T2
T2
T
3
T3
T3
M
a
r
.
10
t
h
Mar._{10th}
Mar.10th
T
1
T1
T1
T
2
T2
T2
T
3
T3
T3
M
a
r
.
11
t
h
Mar._{11th}
Mar.11th
T
1
T1
T1
题意可以理解为有很多个二元组,合理调换二元组内部顺序,在满足first(题目中的宽度)互不相同的前提下最大化second(题目中的高度)之和。
要求first互不相同,这样的限制怎么能不想二分图呢。
如果一个木块宽为s,高为t。就将s与t连边。
但是怎么描述选谁为宽,选谁为高呢。
感觉这里挺妙的。用二分图定向来描述。如果用 s作为宽,那就把这条边定向为 s → t,否则把它定向成 t → s。由于宽度互不相同,因此每个点的出度最多为1.最后答案就是定向后,每个点的
入
度
×
值
=
(
总
度
数
−
出
度
)
×
值
入度\times 值=(总度数-出度)\times 值
入度×值=(总度数−出度)×值 之和。因此我们的任务变成了如何定向,在满足合法的情况下,最大化整张二分图的值。
考虑一个连通块如何定向,显然如果这个连通图的边数比点数多,又抽屉原理,无法做到每个点的出度不大于1.由于题目保证必然存在答案,因此这个连通块要么是一棵树,要么是一棵基环树。
①对于基环树的情况:要想合法,显然必须按照内向基环树来定向,此时答案是固定的,因为大家的总度数都定了,出度必然都是1,计算答案即可。
②对于树的情况:要想合法,显然必须是一棵根向树,非根的点出度都为1,只有根的出度为0。那么此时只有让那个最大点为根是最优的。
#include<bits/stdc++.h>
using namespace std;
int const maxn=2e5+5;
typedef long long ll;
int n,ans;
int s[maxn],t[maxn];
int d[maxn];
int flag[maxn],mx[maxn];
int f[maxn];
int getfa(int x) {return x==f[x]?x:(f[x]=getfa(f[x]));}
void mer(int x,int y)
{
x=getfa(x); y=getfa(y);
if(x==y) {flag[x]=true; return ;}
f[y]=x; flag[x]|=flag[y], mx[x]=max(mx[x],mx[y]);
}
int dc[maxn<<1],R;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d%d",&s[i],&t[i]),
dc[++R]=s[i],dc[++R]=t[i];
sort(dc+1,dc+R+1);
R=unique(dc+1,dc+R+1)-dc-1;
for(int i=1;i<=R;i++)
f[i]=i,mx[i]=dc[i];
for(int i=1;i<=n;i++)
s[i]=lower_bound(dc+1,dc+R+1,s[i])-dc,
t[i]=lower_bound(dc+1,dc+R+1,t[i])-dc,
d[s[i]]++ , d[t[i]]++,
mer(s[i],t[i]);
ll ans=0;
for(int i=1;i<=R;i++)
ans+=1ll*dc[i]*(d[i]-1)
+(getfa(i)==i)*!flag[i]*mx[i];
cout<<ans;
return 0;
}
T
2
T2
T2
T
3
T3
T3
M
a
r
.
14
t
h
Mar._{14th}
Mar.14th
T
1
T1
T1
T
2
T2
T2
T
3
T3
T3
M
a
r
.
15
t
h
Mar._{15th}
Mar.15th
T
1
T1
T1
数竞题,完全不知道怎么想到的。
先考虑整数解。
现在得到了
必须要让
1
2
<
n
m
<
2
\frac{1}{2}<\frac{n}{m}<2
21<mn<2,才能得到正整数解。
考虑到如果
(
p
3
n
,
q
3
m
)
(p^3n,q^3m)
(p3n,q3m)有正整数解,则
(
n
,
m
)
(n,m)
(n,m)必然也有,因为只需要让a,b放大q倍,c,d放大p倍即可。
那么我们就需要找到p,q使
1
2
<
(
p
q
)
3
n
m
<
2
\frac{1}{2}<(\frac{p}{q})^3\frac{n}{m}<2
21<(qp)3mn<2。这样按照上面的方式算出解后a,b放大q倍,c,d放大p倍就得到答案。
三次函数是单调函数,因此直接二分
p
q
\frac{p}{q}
qp即可,由于需要具体的分子与分母,因此需要手写分数。
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int rd()
{
int v=0; char c=getchar(); bool flag=false;
while(c<'0'||c>'9') flag|=(c=='-'),c=getchar();
while('0'<=c&&c<='9') v=(v<<1)+(v<<3)+(c^48),c=getchar();
return flag?-v:v;
}
int n,m;
struct frac{int s,m;};
frac operator +(frac a,frac b)
{
frac ret;
ret.m=a.m/__gcd(a.m,b.m)*b.m;
ret.s=ret.m/a.m*a.s+ret.m/b.m*b.s;
int g=__gcd(ret.m,ret.s);
ret.m/=g; ret.s/=g;
return ret;
}
frac operator /(frac a,int b)
{
int g=__gcd(a.s,b);
a.s/=g; b/=g;
a.m*=b; return a;
}
int chk(int p,int q)
{
if(p*p*p*n>=q*q*q*m*2) return 1;
if(p*p*p*n*2<=q*q*q*m) return -1;
return 0;
}
void find(int &p,int &q)
{
frac low=(frac){0,1},high=(frac){1,1};
while(true)
{
frac mid=(low+high)/2;
p=mid.s,q=mid.m;
int tmp=chk(p,q);
if(tmp==0) return ;
if(tmp<0) low=mid;
else high=mid;
}
}
void solve()
{
bool flag=false;
if(n<m) swap(n,m),flag=true;
int p,q;
find(p,q);
n=p*p*p*n; m=q*q*q*m;
int a=m+n,b=2*n-m,c=m+n,d=2*m-n;
a=q*a; b=q*b; c=p*c; d=p*d;
if(flag) swap(a,c),swap(b,d);
printf("%lld %lld %lld %lld\n",a,b,c,d);
}
signed main()
{
int t=rd();
while(t--) n=rd(),m=rd(),solve();
return 0;
}
T
2
T2
T2
T
3
T3
T3
M
a
r
.
17
t
h
Mar._{17th}
Mar.17th
T
1
T1
T1
不难将问题转化成:
求一个多项式,一个节点的多项式是所有儿子的多项式之积加上x,
最后对于根的多项式F,
A
n
s
=
∑
i
=
1
n
F
[
i
]
×
i
!
×
f
[
i
]
Ans=\sum\limits _{i=1}^{n} F[i]\times i! \times f[i]
Ans=i=1∑nF[i]×i!×f[i]
粗略解释:
这里的多项式是EGF,第i项
a
i
x
1
i
!
a_i\frac{x^1}{i!}
aii!x1的
a
i
a_i
ai就是i次删完的方案数。
要么直接选择根,一次就删完,因此加上
x
1
1
!
\frac{x^1}{1!}
1!x1,要么所有子树的EGF乘起来,用EGF是因为不同子树之间的次数可以打乱顺序,这是可重集排列数。
x
1
1
!
\frac{x^1}{1!}
1!x1直接化为x,思考的时候为了简单不妨考虑成普通多项式,就是上面说的把所有儿子多项式乘起来再加x,只在求答案时加阶乘即可(可能是我狭隘了,对高手肯定不用关是EGF还是啥)
这里考试的时候自己推出来了,但是还是感觉多项式的实质还需要思考
考试的时候就将所有儿子分治ntt乘起来。
复杂度
n
2
l
o
g
2
n
n^2log^2n
n2log2n
复杂度分析:每个点会贡献一个次数,贡献一个次数产生
l
o
g
2
n
log^2n
log2n的复杂度,每个点会对父亲链上每个点贡献一次,因此复杂度
n
2
l
o
g
2
n
n^2log^2n
n2log2n。
考虑优化哪个复杂度,看到对父亲链上每个点都有贡献,是否应该想到轻重链剖分,让它只在父边为轻边是贡献?那么我们的任务转化成处理一条重链时要有复杂度低于
n
2
n^2
n2的做法。
做法基本就出来了。
记录
F
u
′
F'_u
Fu′表示u点所有轻儿子的多项式之积,这个分治ntt直接乘,这部分复杂度是
n
l
o
g
3
n
nlog^3n
nlog3n,就是上面解释的只在轻边贡献复杂度。
那么怎么整合一条重链?
重链顶端u的多项式就是
x
(
F
1
′
∗
F
2
′
∗
.
.
.
∗
F
k
′
+
F
2
′
∗
F
3
′
∗
.
.
.
∗
F
k
′
+
F
3
′
∗
.
.
.
∗
F
k
′
+
.
.
.
+
F
k
′
+
1
)
x(F'_1*F'_2*...*F'_k+F'_2*F'_3*...*F'_k+F'_3*...*F'_k+...+F'_k+1)
x(F1′∗F2′∗...∗Fk′+F2′∗F3′∗...∗Fk′+F3′∗...∗Fk′+...+Fk′+1)
这里为了好叙述,将重链所有点从深到u点标号。
这就很好用分治ntt解决了。
总复杂度
n
l
o
g
3
n
nlog^3n
nlog3n,但是这里3个log里面的n都是不满的,所以轻松过。
还需要总结轻重链剖分的性质
#include<bits/stdc++.h>
using namespace std;
inline int rd()
{
int v=0; char c=getchar(); bool flag=false;
while(c<'0'||c>'9') flag|=(c=='-'),c=getchar();
while('0'<=c&&c<='9') v=(v<<1)+(v<<3)+(c^48),c=getchar();
return flag?-v:v;
}
int const mod=998244353,G=3,maxl=1<<20,maxn=1e5+5;
#define pb push_back
#define rs resize
#define be begin()
#define en end()
#define fi first
#define se second
#define mk make_pair
namespace modular
{
inline int inc(int a,int b) {return (a+b)%mod;}
inline int dec(int a,int b) {return ((a-b)%mod+mod)%mod;}
inline int mul(int a,int b) {return 1ll*a*b%mod;}
inline int qpow(int a,int b)
{int ret=1; for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ret=1ll*ret*a%mod; return ret;}
inline void Inc(int &a,int b) {a=inc(a,b);}
inline void Dec(int &a,int b) {a=dec(a,b);}
inline void Mul(int &a,int b) {a=mul(a,b);}
}using namespace modular;
typedef vector<int> pl;
namespace poly
{
int rev[maxl];
void init(int len) {for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)?len>>1:0);}
void ntt(pl &a,int len,int flag=1)
{
for(int i=0;i<len;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int h=2;h<=len;h<<=1)
{
int wn=qpow(G,(mod-1)/h); if(flag==-1) wn=qpow(wn,mod-2);
for(int j=0;j<len;j+=h)
{
int w=1;
for(int k=j;k<j+h/2;w=mul(w,wn),k++)
{int p=a[k],q=mul(w,a[k+h/2]); a[k]=inc(p,q); a[k+h/2]=dec(p,q);}
}
}
if(flag==-1) {int len_=qpow(len,mod-2); for(int i=0;i<len;i++) a[i]=mul(a[i],len_);}
}
inline int calc_len(int n)
{int len=1; while(len<n) len<<=1; init(len); return len;}
pl operator +(pl a,pl b)
{
int m=a.size(),n=b.size(),
len=max(m,n);
a.rs(len); b.rs(len);
for(register int i=0;i<len;i++)
Inc(a[i],b[i]); return a;
}
pl operator *(pl a,pl b)
{
int m=a.size(),n=b.size(),
len=calc_len(m+n-1);
a.rs(len); b.rs(len);
ntt(a,len); ntt(b,len);
for(register int i=0;i<len;i++)
Mul(a[i],b[i]);
ntt(a,len,-1); a.rs(m+n-1); return a;
}
void output(pl a)
{
for(int i=0;i<a.size();i++) cout<<a[i]<<' ';
}
}using namespace poly;
int n;
vector<int>ch[maxn];
int sz[maxn],tp[maxn];
void dfs1(int u)
{
sz[u]=1;
for(int i=0;i<ch[u].size();i++)
{
dfs1(ch[u][i]); sz[u]+=sz[ch[u][i]];
if(sz[ch[u][i]]>=sz[ch[u][0]])
swap(ch[u][0],ch[u][i]);
}
}
pl F[maxn],F_[maxn];
pl q[maxn]; int num;
pl solve1(int l=1,int r=num)
{
if(l>=r) return q[l];
int mid=l+r>>1;
return solve1(l,mid)*solve1(mid+1,r);
}
pair<pl,pl> solve2(int l=1,int r=num)
{
if(l>=r) {return mk(q[l],q[l]);}
int mid=l+r>>1;
pair<pl,pl> L=solve2(l,mid),R=solve2(mid+1,r);
return mk(L.fi*R.fi,L.se+L.fi*R.se);
}
void dfs2(int u)
{
if(ch[u].size())
tp[ch[u][0]]=tp[u],
dfs2(ch[u][0]);
for(int i=1;i<ch[u].size();i++)
tp[ch[u][i]]=ch[u][i],
dfs2(ch[u][i]);
num=0;
for(int i=1;i<ch[u].size();i++)
q[++num]=F[ch[u][i]];
if(num) F_[u]=solve1(); else F_[u].pb(1);
if(tp[u]==u)
{
num=0; int now=u;
while(ch[now].size())
q[++num]=F_[now],
now=ch[now][0];
if(num) F[u]=solve2().se,Inc(F[u][0],1);
else F[u].pb(1);
F[u].rs(F[u].size()+1);
for(int i=F[u].size()-1;i;i--)
F[u][i]=F[u][i-1]; F[u][0]=0;
}
// output(F[u]); cout<<endl;
}
int main()
{
#ifdef Zplus17
freopen("in.txt","r",stdin);
#endif
cin>>n;
for(int i=2;i<=n;i++)
ch[rd()].pb(i);
dfs1(1); tp[1]=1; dfs2(1);
int ans=0,fac=1;
for(int i=1;i<=n&&i<F[1].size();i++)
Mul(fac,i),
Inc(ans,mul(rd(),mul(F[1][i],fac)));
cout<<ans;
return 0;
}
T
2
T2
T2
T
3
T3
T3
题意是让找到一条直线,既平分多边形周长,又平分多边形面积。
这里先处理周长,再处理面积。
图:
逆时针遍历多边形的每个顶点,假设当前到了i点,此时可以找到对面平分周长的点q,这是可以双指针找到的,设q是在j点为起点的向量上。此时可以判断当前的面积(逆时针转过去那部分面积)是否为一半。
如果不是,发现i向右挪,同时q也向左挪,周长是不会发生改变的。那么假设q点动到j+1号点处时,i点对应到p点。可以感性认知挪动过程中,面积单增或者单减。因此可以先判断是否有解,然后直接二分找到平分面积的位置。复杂度
n
l
o
g
n
nlogn
nlogn。判断是否有解很简单,只要i~q线切法与p~j+1线切法一个比半面积大,一个比半面积小即可。
下来pmh说这不见得是单调而是单峰的,并且曲线二次函数。后面再思考。
总的来说这道题思路还是很简单的。
这道题搞得挺久的。
犯的错误:
1."j+1"在这里应该改成j%n+1。
2.一个重要细节,搞了很久。
注意到q点是必然在边上的,因为我们双指针扫的时候会保证。但是i~i+1可能很短,就是说可能q挪到了j+1,而p点已经出界了。因此我们需要将q~j+1的长度与i~i+1的长度去min,得到最大挪动长度。然后让i挪到p,q挪到r。
#include<bits/stdc++.h>
using namespace std;
inline int rd()
{
int v=0; char c=getchar(); bool flag=false;
while(c<'0'||c>'9') flag|=(c=='-'),c=getchar();
while('0'<=c&&c<='9') v=(v<<1)+(v<<3)+(c^48),c=getchar();
return flag?-v:v;
}
#define db double
#define il inline
db const eps=1e-8;
int const maxn=1e5+5;
il int dcmp(db x) {if(fabs(x)<eps) return 0; else return x<0?-1:1;}
struct pnt {db x,y; pnt(db x=0,db y=0){this->x=x; this->y=y;}};
typedef pnt vct;
vct operator +(vct a,vct b) {return vct(a.x+b.x,a.y+b.y);}
vct operator -(vct a,vct b) {return vct(a.x-b.x,a.y-b.y);}
vct operator *(vct a,db k) {return vct(a.x*k,a.y*k);}
vct operator /(vct a,db k) {return vct(a.x/k,a.y/k);}
il db dot(vct a,vct b) {return a.x*b.x+a.y*b.y;}
il db crs(vct a,vct b) {return a.x*b.y-a.y*b.x;}
il db leng(vct a) {return sqrt(dot(a,a));}
int n; pnt p[maxn]; vct v[maxn];
int main()
{
#ifdef Zplus17
freopen("in.txt","r",stdin);
#endif
n=rd();
for(int i=1;i<=n;i++)
p[i].x=rd(),p[i].y=rd();
db S=0,C=0;
for(int i=1;i<=n;i++)
v[i]=p[i%n+1]-p[i],
C+=leng(v[i]),
S+=crs(p[i],p[i%n+1]);
register int I=1,J=1; db nowc=0,nows=0;
while(I<=n)
{
while(dcmp(nowc+leng(v[J])-C/2)<0)
nowc+=leng(v[J]),
nows+=-crs(p[J],p[I])+crs(p[J],p[J%n+1])+crs(p[J%n+1],p[I]),
J=J%n+1;
db delta=C/2-nowc;
pnt Q=p[J]+v[J]/leng(v[J])*delta;
pnt R=Q+v[J]/leng(v[J])*min(leng(p[J%n+1]-Q),leng(v[I]));
pnt P=p[I]+v[I]/leng(v[I])*leng(R-Q);
db tmps=nows+crs(p[J]-p[I],Q-p[I])-crs(P-p[I],Q-p[I]);
if(dcmp((tmps+crs(P-p[I],Q-p[I])-S/2)*(tmps+crs(R-Q,P-Q)-S/2))<=0)
{
db low=0,high=leng(P-p[I]);
bool opt=crs(P-p[I],Q-p[I])<crs(R-Q,P-Q);
for(register int i=1;i<=60;i++)
{
db mid=(low+high)/2;
pnt pos1=p[I]+v[I]/leng(v[I])*mid,
pos2=Q+v[J]/leng(v[J])*mid;
db _s=tmps+crs(pos1,P)+crs(P,Q)+crs(Q,pos2)+crs(pos2,pos1);
if(_s<=S/2)
if(opt) low=mid;
else high=mid;
else
if(opt) high=mid;
else low=mid;
}
pnt pos1=p[I]+v[I]/leng(v[I])*low,
pos2=Q+v[J]/leng(v[J])*low;
printf("%.8f %.8f\n%.8f %.8f\n",pos1.x,pos1.y,pos2.x,pos2.y);
return 0;
}
nowc-=leng(v[I]),
nows+=-crs(p[J],p[I])-crs(p[I],p[I%n+1])+crs(p[J],p[I%n+1]),
I++;
} puts("-1");
return 0;
}
M
a
r
.
22
t
h
Mar._{22th}
Mar.22th
T
1
T1
T1
计算树上所有路径和有两种常用方法:一种是计算父边的贡献,还有一种是拆成树上差分。
这里采用前者。
答案就是对每个点求其父边的期望贡献之和。
父边的贡献是2*size(n-size),因此我们需要计算E[size],E[size^2]。(期望中只用两个相互独立变量才能直接拆开,这里不能只算E[size])。
E[size]相对简单。对于当前点,计算它的所有倍数在它的子树中的概率(乘以1)之和。如何求一个点j在i中的概率?就是j的所有约数在i中的概率(乘1)之和(这就是j期望意义下有多少个约数在i的子树中)除以j的约数个数即可,注意这里在代码实现上有技巧。
现在考虑如何算E[size^2],一个子树的大小平方就是子树中的点对个数,子树中的点对个数就是
∑
x
是
树
中
的
一
个
点
以
x
为
l
c
a
的
点
对
个
数
\sum \limits_{x是树中的一个点}以x为lca的点对个数
x是树中的一个点∑以x为lca的点对个数。问题转化为求如何计算期望下有多少点对以一个点u为lca.
这个问题等价于在问按照这样的生成方式生成两棵树T1,T2,期望有多少点对(x,y)满足x在T1中是u的子孙,y在T2中是u的子孙,并且不存在一个比u更大的u’也满足x在T1中是u’的子孙,y在T2中是u’的子孙。
证明等价:
因为在一棵树中,如果 u 是 x, y 的 LCA,那么 x ∼ u 与 y ∼ u 是不交的,所以它们是互相独立的。因此,这部分的答案和两棵树的答案是一样的。
求法:记f[u]表示u点的这个值,考虑一个简单容斥,令num[i]表示期望有多少点对(x,y),在x在T1中是i的儿子,y在T2中是i的儿子。
num[u]=E[size]*E[size].
现在要去掉的是不合法的(x,y),它们分别在T1,T2中都有祖先u’,为了不减重,我们将这个减在分别在T1,T2共有的祖先中编号最大的那个身上(就是说下面的公式中的f[j]不要写成E[size_j]*E[size_j]了)。
因此
f
[
i
]
=
E
[
s
i
z
e
]
×
E
[
s
i
z
e
]
−
∑
i
∣
j
p
[
j
]
×
p
[
j
]
×
f
[
j
]
f[i]=E[size]\times E[size] -\sum_{i|j}p[j]\times p[j]\times f[j]
f[i]=E[size]×E[size]−∑i∣jp[j]×p[j]×f[j]
p[j]乘两次的原因是要在两棵树中同时是i的子孙 。
还需要总结
注:考试的时候一直觉得和一道叫苹果树的HAOI的题很像,也是计算所有的方案数,然后枚举每个点和他的子树大小计算父边的贡献,然后发现原题是只要比大小就行,这道题是必须整除才行,分析了很久,始终发现逃不开指数级枚举,,,
代码的可读性是很高的。
#include<bits/stdc++.h>
using namespace std;
int const maxn=3E5+5;
int n,mod;
int d[maxn],inv[maxn];
int p[maxn],sz[maxn],sqsz[maxn],f[maxn];
namespace modular
{
inline int inc(int a,int b) {return (a+b)%mod;}
inline int dec(int a,int b) {return ((a-b)%mod+mod)%mod;}
inline int mul(int a,int b) {return 1ll*a*b%mod;}
inline int qpow(int a,int b)
{int ret=1; for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ret=1ll*ret*a%mod; return ret;}
inline void Inc(int &a,int b) {a=inc(a,b);}
inline void Dec(int &a,int b) {a=dec(a,b);}
inline void Mul(int &a,int b) {a=mul(a,b);}
}using namespace modular;
int main()
{
#ifdef Zplus17
freopen("in.txt","r",stdin);
#endif
cin>>n>>mod;
for(int i=1;i<=n;i++)
for(int j=i+i;j<=n;j+=i)
d[j]++;
for(int i=1;i<=n;i++)
inv[i]=qpow(d[i],mod-2);
int ans=0;
for(int i=n;i;i--)
{
p[i]=sz[i]=1;
for(int j=i+i;j<=n;j+=i) p[j]=0;
for(int j=i+i;j<=n;j+=i)
{
p[j]=mul(p[j]+1,inv[j]);
for(int k=j+j;k<=n;k+=j)
Inc(p[k],p[j]);
Inc(sz[i],p[j]);
}
f[i]=mul(sz[i],sz[i]);
for(int j=i+i;j<=n;j+=i)
Dec(f[i],mul(f[j],mul(p[j],p[j])));
for(int j=i;j<=n;j+=i)
Inc(sqsz[i],mul(p[j],f[j]));
Inc(ans,mul(sz[i],n));
Dec(ans,sqsz[i]);
}
cout<<mul(ans,2);
return 0;
}
T
2
T2
T2
题意:有一个所有边与坐标轴平行多边形,用尽量少的刀将它劈成一些矩形。一刀是合法的:这一刀的从一个边界点开始,到另一个边界点结束。还有一个条件,可以一刀水平穿过整个图形,但是好像此题没用。
一个所有边与坐标轴平行多边形是矩形,当且仅当它是凸的。也就是说,我们要去掉所有的凹点(凹点就是凹进去的顶点,如凸字就有两个凹点)
现在考虑一刀的两个端点:1.显然都不是凸点。(不合法)2.显然不都是边上点(容易发现这必然不优)
因此,这要么是一个凹点,一个边上点,要么都是凹点。
考虑一个凹点如果被切开,那么会变成一个凸点,一个边上点。换句话说,这个凹点就消失了,而当所有凹点都消失,我们就成功了。而只要合法地切,不管怎么切,凹点是不会凭空产生的。
因此分析后可知 切的数量=凹点总个数-特殊刀数 特殊刀就是我们上面说的一刀下去两个端点都是凹点的切法。
而凹点总个数是固定的,可以一开始就计算好常数,现在的任务是最大化特殊刀数,才能最小化切的数量。
因为凹顶点是不会凭空产生的(切割只能产生边界点和凸顶点),所以所有这样的操作在一开始就是合法的操作了。我们考虑预处理出原多边形中所有这样的操作,这是容易的:对于竖直的,直接枚举即可,看是否第i列与第i-1列上面不一样高,下面也不一样深(有一个不满足都不行,否则就不都是凹点而存在边上点了);对于水平的,用一个单调栈即可处理,当前把队尾比自己高的去掉后,看剩下的是否与自己一样。这部分时间复杂度是线性的。
由于一开始这些操作都是合法的,而它们可能会变得不合法(两个端点变得分属两个多边形),所以在最优方案中,可以先进行这样的操作,只需要保证它们都还是合法的即可。而这等价于每个操作的端点都没有被切开,也就是任意两次操作都不相交。于是,我们的问题变成了求这些操作的最大独立集:两个操作之间有边,当且仅当它们相交。而水平操作一定不交,竖直操作也不交,故这是二分图。根据 Konig 定
理,变为了二分图最大匹配。可以使用 Dinic 算法在O(E√V ) = O(n^2.5)解决。
但是我们实际上还没有用到原多边形性质。注意到每个竖直线的横坐标都是不同的。而每个切割都完全在多边形内,所以对于每个水平操作,与它相交的竖直操作对应了横坐标的一段连续区间。所以实际上变为了 p 个点,q 个区间,如果 xi ∈ [lj , rj ],那么它们之间有边的最大匹配问题。这个是非常容易解决的:按照从小到大的顺序依次考虑每个点,贪心匹配可以匹配的区间中,rj 最小的那个即可。用堆即可很好地维护。时间复杂度:O(nlogn)。
#include<bits/stdc++.h>
using namespace std;
inline int rd()
{
int v=0; char c=getchar(); bool flag=false;
while(c<'0'||c>'9') flag|=(c=='-'),c=getchar();
while('0'<=c&&c<='9') v=(v<<1)+(v<<3)+(c^48),c=getchar();
return flag?-v:v;
}
#define pb push_back
#define mk make_pair
int const maxn=5E5+10;
int n;
int u[maxn],d[maxn];
vector<int>L; vector<pair<int,int> >R;
void init(int *h)
{
static int stk[maxn],tp; tp=0;
for(int i=2;i<=n;i++)
{
if(h[i]==h[i-1]) continue;
if(h[i]>h[i-1]) stk[++tp]=i-1;
else
{
while(tp&&h[stk[tp]]>h[i]) tp--;
if(tp&&h[stk[tp]]==h[i]) R.pb(mk(stk[tp--]+1,i));
}
}
}
priority_queue<int,vector<int>,greater<int> >q;
int main()
{
#ifdef Zplus17
freopen("in.txt","r",stdin);
#endif
n=rd();
for(int i=1;i<=n;i++)
u[i]=rd(),d[i]=rd();
int ans=0;
for(int i=2;i<=n;i++)
{
ans+=(u[i]!=u[i-1])+(d[i]!=d[i-1]);
if(u[i]!=u[i-1]&&d[i]!=d[i-1]) L.pb(i);
}
init(u); init(d);
sort(R.begin(),R.end());
ans-=L.size()+R.size();
for(int i=0,j=0;i<L.size();i++)
{
while(j<R.size()&&R[j].first<=L[i]) q.push(R[j++].second);
while(q.size()&&q.top()<L[i]) q.pop();
if(q.size()&&q.top()>=L[i]) ans++,q.pop();
} cout<<ans;
return 0;
}
T 3 T3 T3
M
a
r
.
22
t
h
Mar._{22th}
Mar.22th
T
1
T1
T1
这是一道大力分类讨论。多复习这道题
一读题被吓到了,没敢深入思考。
看到树上最大距离,先想到直径。
题解非常仔细:
#include<bits/stdc++.h>
using namespace std;
inline int rd()
{
int v=0; char c=getchar(); bool flag=false;
while(c<'0'||c>'9') flag|=(c=='-'),c=getchar();
while('0'<=c&&c<='9') v=(v<<1)+(v<<3)+(c^48),c=getchar();
return flag?-v:v;
}
int const maxn=3E5+5;
#define pb push_back
vector<int>e[maxn]; int root;
int dep[maxn],tmp_dep[maxn],D;
int cnt1[maxn],cnt2[maxn],cnt;
void dfs_(int u,int ff=0)
{
dep[u]=dep[ff]+1;
for(int v:e[u])
if(v!=ff) dfs_(v,u);
}
void dfs(int u,int ff,int bl)
{
dep[u]=dep[ff]+1;
cnt1[bl]+=(dep[u]==D), //这里D相当于半径D/2
cnt2[bl]+=(dep[u]==D-2); //这里D-2相当于原树中D/2-1
for(int v:e[u])
if(v!=ff) dfs(v,u,bl);
}
int main()
{
#ifdef Zplus17
freopen("in.txt","r",stdin);
#endif
int n_=rd(),n=n_;
for(int u,v,i=1;i<n_;i++)
u=rd(),v=rd(),n++,
e[u].pb(n),e[n].pb(v),
e[v].pb(n),e[n].pb(u);
dep[0]=-1; dfs_(1);
int a1=0;
for(int i=1;i<=n;i++)
if(dep[i]>dep[a1])
a1=i;
dfs_(a1);
int a2=0;
for(int i=1;i<=n;tmp_dep[i]=dep[i],i++)
if(dep[i]>dep[a2])
a2=i;
D=dep[a2]/2;
dfs_(a2);
for(int i=1;i<=n;i++)
if(dep[i]==D&&tmp_dep[i]==D)
{root=i; break;}
dep[root]=0;
for(int v:e[root])
dfs(v,root,v),
cnt+=(cnt1[v]>0);
if(cnt>=4) {cout<<D; return 0;}
if(cnt==3)
{
int num=0;
for(int v:e[root])
num+=(cnt1[v]==1);
cout<<D-(num==3&&n_%2==0);
return 0;
}
int num=0;
for(int v:e[root])
num+=cnt1[v]>1;
if(num==2) {cout<<D; return 0;}
if(num==1) {cout<<D-(n_%2==0); return 0;}
if(n_%2==1) {cout<<D-1; return 0;}
num=0;
for(int v:e[root])
if(!cnt1[v]) num+=cnt2[v];
if(num>=2) {cout<<D-1; return 0;}
int num_=0;
for(int v:e[root])
num_+=cnt1[v]&&cnt2[v]>1;
if(num_==2) {cout<<D-1; return 0;}
if(num_==1) {cout<<D-1-(num!=1); return 0;}
cout<<D-2; return 0;
}
T
2
T2
T2
T
3
T3
T3
M
a
r
.
24
t
h
Mar._{24th}
Mar.24th
T
1
T1
T1
性质:一个区间有解,它的子区间一定有解。
因此我们考虑对每个位置i,
求出
l
i
l_i
li表示以i为右端点的所有不合法区间中,
最靠右的左端点位置。
关于同余有一个性质:
x
≡
a
(
m
o
d
k
m
)
→
x
≡
a
(
m
o
d
m
)
x\equiv a(mod \ km) \rightarrow x\equiv a(mod~m)
x≡a(mod km)→x≡a(mod m)
而知道了x对a1,a2,a3…的余数,又可以中国剩余定理反推回x对它们的lcm的余数。
我们考虑什么时候两个方程发生冲突:
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
x\equiv a1(mod~m1) \\ x\equiv a2(mod~m2)
x≡a1(mod m1)x≡a2(mod m2)
根据上面的性质,我们考虑把m1,m2分解素约数。
那么这第一个式子等价于
x
≡
a
1
(
m
o
d
p
1
c
1
)
x \equiv a1(mod~p1^{c1})
x≡a1(mod p1c1)
x
≡
a
1
(
m
o
d
p
2
c
2
)
.
.
.
\\x \equiv a1(mod~ p2^{c2}) \\...
x≡a1(mod p2c2)...
第二个式子等价于
x
≡
a
2
(
m
o
d
p
1
c
1
′
)
x
≡
a
2
(
m
o
d
p
2
c
2
′
)
.
.
.
x\equiv a2(mod~ p1^{c1'}) \\x \equiv a2(mod~ p2^{c2'})\\...
x≡a2(mod p1c1′)x≡a2(mod p2c2′)...
(这里p1=2,p2=3,p3=5…,如果没有pi因子,ci就为0)
对于每对
$x\equiv a1(mod ~pi^{ci})\x\equiv a2(mod~ pi^{ci’})\$
判定
x
≡
a
1
(
m
o
d
p
i
m
i
n
(
c
i
,
c
i
′
)
)
x
≡
a
2
(
m
o
d
p
i
m
i
n
(
c
i
,
c
i
′
)
)
x \equiv a1(mod~ pi^{min(ci,ci')})\\ x \equiv a2(mod ~pi^{min(ci,ci')})
x≡a1(mod pimin(ci,ci′))x≡a2(mod pimin(ci,ci′))
是否有a1与a2同余即可。
如果不同余,显然两个方程发生冲突。
因此我们做法就出来了。
对于当前
x
≡
r
(
m
o
d
d
)
x\equiv r(mod~ d)
x≡r(mod d),我们将d分解素约数(注1),
对每个
p
i
c
i
pi^{ci}
pici,枚举所有
j
,
1
≤
j
≤
c
i
j,1\leq j \leq ci
j,1≤j≤ci,看最后一个有
p
i
j
pi^j
pij这一约数的方程是否与当前在
m
o
d
p
i
j
mod ~pi^j
mod pij冲突。
如果冲突,就用这个方程的位置更新l[i]。
这样所有l[i]求出来后面的就太简单了,看代码就行了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int rd()
{
int v=0; char c=getchar(); bool flag=false;
while(c<'0'||c>'9') flag|=(c=='-'),c=getchar();
while('0'<=c&&c<='9') v=(v<<1)+(v<<3)+(c^48),c=getchar();
return flag?-v:v;
}
inline void wt(int i)
{
int fi[20],p=1;
if(!i) fi[p++]=0;
else while(i) fi[p++]=i%10,i/=10;
for(int j=p-1;j;j--) putchar(fi[j]+48); putchar('\n');
}
typedef pair<int,int> pii;
#define mk make_pair
#define fi first
#define se second
int const maxn=1E6+5;
int n,lp[maxn]; int s[maxn];
pii lst_ok[maxn],lst_nok[maxn];
int v[maxn];
void sieve()
{
int n=1E6+1; static int p[maxn],rr=0;
for(int i=2;i<=n;i++)
{
if(!v[i]) p[++rr]=v[i]=i;
for(int j=1;j<=rr&&p[j]<=v[i]&&p[j]*i<=n;j++)
v[p[j]*i]=p[j];
}
}
signed main()
{
#ifdef Zplus17
freopen("in.txt","r",stdin);
#endif
sieve();
n=rd();
for(int i=1;i<=n;i++)
{
int d=rd(),r=rd();
while(d>1)
{
int p=v[d],c=0;
while(!(d%p)) d/=p,c++;
for(int j=1,P=p;j<=c;j++,P*=p)
if(lst_ok[P].fi==r%P)
lp[i]=max(lp[i],lst_nok[P].se),
lst_ok[P].se=i;
else
lp[i]=max(lp[i],lst_ok[P].se),
lst_nok[P]=lst_ok[P],
lst_ok[P]=mk(r%P,i);
}
}
for(int i=1;i<=n;i++)
lp[i]=max(lp[i],lp[i-1]),
s[i]=s[i-1]+(i-lp[i]);
int m=rd();
while(m--)
{
int l=rd(),r=rd();
int p=lower_bound(lp+1,lp+n+1,l)-lp-1;
if(p<l) wt(s[r]-s[l-1]);
else if(p>r) wt((r-l+2)*(r-l+1)/2);
else wt((p-l+2)*(p-l+1)/2+s[r]-s[p]);//由于左右端点可以重复,所以是C(p-l+1+1,2)
}
return 0;
}
T
2
T2
T2
T
3
T3
T3