做题记录

就你这水平怎么打 NOIP 啊。

很显然的思路,但是写了 2.76K,之后看题解发现有很短的写法,吓哭了。

首先我们可以求出每个点从根节点走到这个点的值的变化量 \(v\),令这个点与其父亲的边为 \(i\),那么初值 \(x\) 应满足 \(L_i -v \le x \le R_i-v\)

变化量 \(v\) 可以用线段树维护,相当于是子树加和,所以找到 DFS 序然后线段树区间加单点查询即可。

而且如果能从根节点走到这个点,说明初值 \(x\) 也一定满足这个路径上所有点的限制,这个 DFS 算一下区间交就行了。

现在我们知道了每一个点合法的范围 \([L'_i,R'_i]\),很自然想到差分,但是值域很大,所以需要把所有的 \(L'_i,R'_i,x\) 离散化。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e6+10;
int n,Q;
int h[N],e[N],ne[N],idx;
int L[N],R[N];
int dfn[N],timestamp,sz[N];
int q[N],top,d[N],x[N],tot;
struct node
{
	int l,r;
	int sum,add;
}tr[N*4];
void add(int a,int b)
{
	e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs(int u,int father)
{
	sz[u]=1;dfn[u]=++timestamp;
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father)continue;
		//L[j]=max(L[j],L[u]);R[j]=min(R[j],R[u]);
		dfs(j,u);
		sz[u]+=sz[j];
	}
}
void pushup(int u){tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;}
void pushdown(int u)
{
	node &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
	if(root.add)
	{
		left.add+=root.add;left.sum+=(left.r-left.l+1)*root.add;
		right.add+=root.add;right.sum+=(right.r-right.l+1)*root.add;
		root.add=0;
	}
}
void build(int u,int l,int r)
{
	if(l==r)tr[u]={l,r};
	else
	{
		tr[u]={l,r};
		int mid=l+r>>1;
		build(u<<1,l,mid);build(u<<1|1,mid+1,r);
		pushup(u);
	}
}
void update(int u,int l,int r,int d)
{
	if(tr[u].l>=l&&tr[u].r<=r)
	{
		tr[u].sum+=(tr[u].r-tr[u].l+1)*d;
		tr[u].add+=d;
		return;
	}
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(l<=mid)update(u<<1,l,r,d);
	if(r>mid)update(u<<1|1,l,r,d);
	pushup(u);
}
int query(int u,int l,int r)
{
	if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	int res=0;
	if(l<=mid)res=query(u<<1,l,r);
	if(r>mid)res+=query(u<<1|1,l,r);
	return res;
}
int find(int x)
{
	int l=1,r=tot;
	while(l<r)
	{
		int mid=l+r>>1;
		if(q[mid]>=x)r=mid;
		else l=mid+1;
	}
	return l;
}
void solve(int u,int father,int left,int right)
{
	
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father)continue;
		L[j]=max(L[j],left);R[j]=min(R[j],right);
		solve(j,u,L[j],R[j]);
	}
}
signed main()
{
	//freopen("calc3.in","r",stdin);
	//freopen("calc.out","w",stdout);
	memset(h,-1,sizeof h);
	scanf("%lld%lld",&n,&Q);
	for(int i=1;i<n;i++)
	{
		int p,a,b;
		scanf("%lld%lld%lld",&p,&a,&b);
		add(p,i+1);
		L[i+1]=a;R[i+1]=b;
	}
	dfs(1,-1);
	build(1,1,n);
	for(int i=1;i<=n;i++)
	{
		char op;int x;
		scanf("%s%lld",&op,&x);
		if(sz[i]==1)continue;
		if(op=='+')update(1,dfn[i]+1,dfn[i]+sz[i]-1,x);
		else update(1,dfn[i]+1,dfn[i]+sz[i]-1,-x);
	}
	for(int i=2;i<=n;i++)
	{
		int sum=query(1,dfn[i],dfn[i]);
		L[i]-=query(1,dfn[i],dfn[i]),R[i]-=query(1,dfn[i],dfn[i]);
	}
	solve(1,-1,-1e18,1e18);
	for(int i=2;i<=n;i++)q[++top]=L[i],q[++top]=R[i];
	for(int i=1;i<=Q;i++)
	{
		scanf("%lld",&x[i]);
		q[++top]=x[i];
	}
	sort(q+1,q+top+1);
	tot=unique(q+1,q+top+1)-q-1;
	for(int i=2;i<=n;i++)
	{
		int left=find(L[i]),right=find(R[i]);
		//cout<<L[i]<<' '<<R[i]<<endl;
		if(left>right)continue;
	//	cout<<left<<' '<<right<<endl;
		d[left]++;d[right+1]--;
	}
	for(int i=1;i<=tot;i++)
		d[i]+=d[i-1];
	for(int i=1;i<=Q;i++)
		printf("%lld\n",d[find(x[i])]+1);
	return 0;
}

考虑类似 kruskal 求最小生成树的做法,要让删掉的边权和最小,就是让剩下的边权和最大。

所以按边权从大到小排序,如果两个连通块都没被标记,就加边。

用并查集维护连通块以及是否被标记。

最后用总边权减去所加的边权和。

清新计数题,但是想 20min 写 1h。

首先想到枚举 C 和 F 的左上角,这样上面的横确定是第几行,竖确定是第几列了。

先来看 C 如何计算。

如果下面的横也确定横坐标,那就是上面的横所能延申的长度与下面的横所能延申的长度的积。

维护延申长度可以递推每一个点向右碰到的第一个 \(1\),然后直接计算。

但是这样需要枚举下面的横是第几行,复杂度是 \(O(n^2m)\) 的,需要优化。

发现枚举下面横的行数的过程还是可以递推出从枚举的这个点向下走碰到的第一个 \(1\)

假设现在枚举的点是 \((i,j)\),向右延申的最长长度为 \(lenx_{i,j}\),向下延申的最长长度为 \(leny_{i,j}\)

下面的横的行数的范围应该在 \([i+2,leny_{i,j}]\).

左边界是 \(i+2\) 因为这两个横不能相邻。

所以现在 C 的方案数为 \(\sum_{x=i+2}^{leny_{i,j}} lenx_{i,j} \times lenx_{x,j}\)

根据乘法分配律,上面的式子转化为 \(lenx_{i,j} \times \sum_{x=i+2}^{leny_{i,j}} lenx_{x,j}\)

对于每一列维护 \(lenx\) 的后缀和即可 \(O(1)\) 计算。

F 的方案数是类似的,但是还需要计算第二个横下面的竖的方案。

同理可知,我们对每一列维护 \(lenx \times leny\) 的后缀和即可。

坑点其实不多,注意多测,long long。

代码很好写,我写得很好笑。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1010,mod=998244353;
int t,id;
int n,m,c,f;
char mp[N][N];
int nex[N][N],ney[N][N];//(i,j)向右/下走到的第一个1
int lenx[N][N],leny[N][N];
int sum[N][N],sum2[N][N];
signed main()
{
	cin>>t>>id;
	while(t--)
	{
		cin>>n>>m>>c>>f;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				cin>>mp[i][j];
		memset(nex,0,sizeof nex);memset(lenx,0,sizeof lenx);
		memset(ney,0,sizeof ney);memset(leny,0,sizeof leny);
		memset(sum,0,sizeof sum);memset(sum2,0,sizeof sum2);
	//	for(int i=1;i<=n;i++)mp[i][m+1]='1';
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m+1;j++)
				if(mp[i][j]=='1')
					nex[i][j-1]=j,ney[i-1][j]=i;
		for(int i=n;i>=1;i--)
			for(int j=1;j<=m;j++)
			{
				if(mp[i][j]=='1'||ney[i][j])continue;
				ney[i][j]=ney[i+1][j];
				if(!ney[i][j])ney[i][j]=n+1;
				leny[i][j]=(ney[i][j]-i-1);
			}
		for(int i=1;i<=n;i++)
			for(int j=m;j>=1;j--)
			{
				if(mp[i][j]=='1'||nex[i][j])continue;
				nex[i][j]=nex[i][j+1];
				if(!nex[i][j])nex[i][j]=m+1;
				lenx[i][j]=(nex[i][j]-j-1);
			}
		for(int i=n;i>=1;i--)
		{
			for(int j=1;j<=m;j++)
				sum[i][j]=sum[i+1][j]+lenx[i][j],sum2[i][j]=sum2[i+1][j]+lenx[i][j]*leny[i][j];
		}
		int cntc=0,cntf=0;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
			{
				int l=i+2,r=i+leny[i][j];
				//cout<<l<<' '<<r<<endl;
				if(l<=r)cntc+=(sum[l][j]-sum[r+1][j])*(lenx[i][j])%mod;
				if(l<=r)cntf+=(sum2[l][j]-sum2[r][j])*(lenx[i][j])%mod;
				cntc%=mod;cntf%=mod;
			}
		cout<<(c*cntc)%mod<<' '<<(f*cntf)%mod<<endl;
	}	
} 
posted @ 2025-11-20 11:22  infinite2021  阅读(7)  评论(0)    收藏  举报