我回来了

纵使日薄西山

即便看不到未来

此时此刻的光辉

盼君勿忘

 

开幕雷击

 

 

题解

大概想了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()));
}