我回来了
纵使日薄西山
即便看不到未来
盼君勿忘
开幕雷击
题解
大概想了30min
然后从9:20开始写,写到了11:00(果然,老年选手的码力急剧下降)
然后从11:00调到了考试结束13:00。。。
后来发现要加一个倍增,于是又花30min写了一个倍增
13:30交卷。。。
回归正题
正难则反,我们来统计不合法的方案数
然后就和这道题的思路差不多了:https://blog.csdn.net/C20180602_csq/article/details/104505857(T3)
只不过我们是求所有矩形的面积并
我们可以利用扫描线+线段树直接解决
这里的线段树利用了矩形有加必有减的性质,可以直接对每一个节点维护当前线段被整个覆盖后的次数
发现这个是可以简单合并的
然后就做完了O(nlnnlogn)
对了,还有一个调了我2h的错:在路径端点呈祖先-儿子关系时,要用儿子到祖先的最近儿子的补集来计算矩形
所以还要多写一个倍增。。。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
int fir[N],to[2*N],nxt[2*N],cnt;
void adde(int a,int b)
{
to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;
to[++cnt]=a;nxt[cnt]=fir[b];fir[b]=cnt;
}
#define LOG 18
int pos[N],siz[N],dfn;
int f[N][LOG+2],dep[N];
void dfs(int u)
{
pos[u]=++dfn;siz[u]=1;
dep[u]=dep[f[u][0]]+1;
for(int v,p=fir[u];p;p=nxt[p]){
v=to[p];
if(v!=f[u][0]){
f[v][0]=u;
dfs(v);
siz[u]+=siz[v];
}
}
}
int getk(int u,int k)
{
for(int i=LOG;i>=0;i--)
if(k&(1<<i)) u=f[u][i];
return u;
}
int getd(int u,int d)
{
return getk(u,dep[u]-d);
}
bool pd(int u,int v) //1 means u,v have an ancient-son relationship. otherwise 0
{
if(pos[u]>pos[v]) swap(u,v);
if(pos[v]<pos[u]+siz[u]) return 1;
return 0;
}
struct qnode{
int l,r,h,op;
qnode(){}
qnode(int x,int y,int z,int w){l=x;r=y;h=z;op=w;}
bool operator < (const qnode &t)const{
return h<t.h||(h==t.h&&op>t.op);
}
}q[60*N];
#define lc i<<1
#define rc i<<1|1
struct node{
int l,r,cnt,sum,len;
}a[N<<2];
void build(int i,int l,int r)
{
a[i].l=l;a[i].r=r;a[i].len=r-l+1;
if(l==r)return;
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
void pushup(int i)
{
if(a[i].cnt>0)a[i].sum=a[i].len;
else{
if(a[i].l==a[i].r) a[i].sum=0;
else a[i].sum=a[lc].sum+a[rc].sum;
}
}
void insert(int i,int l,int r,int k)
{
if(r<a[i].l||a[i].r<l)return;
if(l<=a[i].l&&a[i].r<=r){
a[i].cnt+=k;
pushup(i);
return;
}
insert(lc,l,r,k);insert(rc,l,r,k);
pushup(i);
}
int main()
{
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
int n,i,j,u,v,m=0;
int lu,ru,lv,rv;
n=gi();
for(i=1;i<n;i++){u=gi();v=gi();adde(u,v);}
dfs(1);
for(j=1;j<=LOG;j++)
for(i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
for(i=1;i<=(n>>1);i++){
for(j=i+i;j<=n;j+=i){
lu=pos[i];ru=pos[i]+siz[i]-1;
lv=pos[j];rv=pos[j]+siz[j]-1;
if(!pd(i,j)){
if(lu<lv){
q[++m]=qnode(lu,ru,lv,1);
q[++m]=qnode(lu,ru,rv+1,-1);//(lu,ru)(lv,rv)
}
else if(lv<lu){
q[++m]=qnode(lv,rv,lu,1);
q[++m]=qnode(lv,rv,ru+1,-1);//(lv,rv)(lu,ru)
}
}
else{
u=i;v=j;
if(dep[u]>dep[v]){swap(u,v);swap(lu,lv);swap(ru,rv);}
u=getd(v,dep[u]+1);
lu=pos[u];ru=pos[u]+siz[u]-1;
q[++m]=qnode(1,lu-1,lv,1);
q[++m]=qnode(1,lu-1,rv+1,-1);//(1,lu-1)(lv,rv)
q[++m]=qnode(lv,rv,ru+1,1);
q[++m]=qnode(lv,rv,n+1,-1);//(lv,rv)(ru+1,n)
}
//printf("%d %d %d %d\n",lu,ru,lv,rv);
}
}
sort(q+1,q+m+1);
build(1,1,n);
long long sum=0;
for(i=1;i<=m;i++){
if(i>1)sum+=1ll*(q[i].h-q[i-1].h)*a[1].sum;
insert(1,q[i].l,q[i].r,q[i].op);
}
printf("%lld",1ll*n*(n-1)/2-sum);
//printf("\n%d %lld",m,sum);
}
题解
这道题是比较好想的(然后我没有想出来)
官方题解已经讲得很清楚了
(话说好像我之前做过这道题的,只是我忘了。。。然后这是全场的签到题。。。)
其实还有一种思考的思路就是把矩阵转为二分图来思考,大概方法也差不多,先分离第一行代表点的环,然后推式子
但是这样做好像不能很好地计算A数组,所以我就放弃了。。。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10000005
const int mod=998244353;
int fac[N],inv[N],finv[N],g[N];
void shai()
{
fac[0]=fac[1]=inv[0]=inv[1]=1;
finv[0]=finv[1]=1;
for(int i=2;i<=10000000;i++){
fac[i]=1ll*i*fac[i-1]%mod;
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
finv[i]=1ll*finv[i-1]*inv[i]%mod;
}
}
int main()
{
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
shai();
int n,i,sumg=0,ans=0;
scanf("%d",&n);
g[0]=1;g[1]=0;
for(i=2;i<=n;i++){
sumg+=g[i-2];if(sumg>=mod)sumg-=mod;
g[i]=1ll*sumg*inv[2]%mod*inv[i]%mod;
ans=(1ll*ans+1ll*g[i]*fac[i]%mod*fac[i])%mod;
}
printf("%d",ans);
}
题解
这题让我回忆起了在WC2019上听模拟费用流的ZZ经历,全程懵逼。。
这道题就是模拟费用流
现在感觉好了
这道题有一个关键的性质:树高是logn的
于是我们就可以想到暴力爬链(Master.Yi大佬有云:遇到随机树就要想到暴力爬链)
我们可以先贪心地想到:
每爬到一个祖先,我们一定会走一个离祖先最近的可行点
然后加上费用???
当然不行
我们要学会反悔:
之前有一些点花了一定的代价才走到下面的某一个点,但是当前点却想要往上走
路径交叉显然不优,我们可以把这两个点了目的地互换位置
具体怎么实现呢?
当然不能直接模拟这个互换位置的操作
有一个巧妙的思路:
把向下走设为正,向上走设为负
统计一下每一条边的上下流量差
如果为正,说明更多的点会向下走
若此时当前点要向上走,那么就可以抵消一个向下走的点
把这些路径设为负数即可
注意要在最后更新一下每个点的最近儿子
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=10*num+c-48;c=getchar();}
return num*flg;
}
#define N 300005
#define lc (i<<1)
#define rc (i<<1|1)
int n,c[N],cap[N];
struct node{
int x,p;
node(){}
node(int q,int w){x=q;p=w;}
bool operator < (const node &t)const{return x<t.x;}
node operator + (const int &t)const{return node(x+t,p);}
}f[N];
void pushup(int i)
{
if(c[i]) f[i]=node(0,i);
else f[i]=min(lc<=n?f[lc]+(cap[lc]<=0?1:-1):f[0],rc<=n?f[rc]+(cap[rc]<=0?1:-1):f[0]);
}
int solve(int s)
{
int ret=1e9,k,dis=0,i;
for(i=s;i;dis+=(cap[i]>=0?1:-1),i>>=1)
if(f[i].x+dis<ret){
ret=f[i].x+dis;
k=i;
}
for(i=s;i!=k;i>>=1) cap[i]++;
for(i=f[k].p;i!=k;i>>=1) cap[i]--;
c[f[k].p]--;
for(i=f[k].p;i;i>>=1)pushup(i);
for(i=s;i;i>>=1)pushup(i);
return ret;
}
int main()
{
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
int m,i;long long ans=0;
n=gi();m=gi();
f[0]=node(0x3f3f3f3f,0);
for(i=1;i<=n;i++)c[i]=gi();
for(i=n;i>=1;i--)pushup(i);
printf("%lld",ans+=solve(gi()));
for(i=2;i<=m;i++)printf(" %lld",ans+=solve(gi()));
}