模拟赛好题1

gtm和joke的星球

https://www.gxyzoj.com/d/hzoj/p/P2877

虽然但是,板子。

最小斯坦纳树,就是求一棵树,使其经过 k 个关键点,求最小边权和,这是一个 NP 问题

考虑 dp,设 \(f_{i,s}\) 当前根为 i,包含状态为 s,分为两种情况:

  1. i 度数为 1,找和它相连的 j,\(f_{i,s}=f_{j,s}+w(i,j)\),显然可以最短路

  2. 度数不为 1,相当于拆成若干子树,\(f_{i,s}=f_{i,t}+f_{i,s\ xor\ t}\)

点击查看代码
#include<cstdio>
#include<queue>
using namespace std;
const int N=105,M=505;
int n,m,k,head[N],edgenum,a[N],f[N][1050];
struct edge{
	int to,nxt,val;
}e[M*2];
void add_edge(int u,int v,int w)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	e[edgenum].val=w;
	head[u]=edgenum;
}
queue<int> q;
bool inq[N];
void spfa(int s)
{
	for(int i=1;i<=n;i++)
	{
		if(f[i][s]<1e9) q.push(i),inq[i]=1;
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=0;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(f[v][s]>f[u][s]+e[i].val)
			{
				f[v][s]=f[u][s]+e[i].val;
				if(!inq[v]) q.push(v),inq[v]=0;
			}
		}
	}
}
int main()
{
	freopen("steiner.in","r",stdin);
	freopen("steiner.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<(1<<k);j++)
		{
			f[i][j]=1e9;
		}
	}
	for(int i=1;i<=k;i++)
	{
		scanf("%d",&a[i]);
		f[a[i]][1<<(i-1)]=0;
	}
	for(int s=1;s<(1<<k);s++)
	{
		for(int t=s&(s-1);t;t=s&(t-1))
		{
			if(t<(s^t)) continue;
			for(int i=1;i<=n;i++)
			{
				f[i][s]=min(f[i][s],f[i][t]+f[i][s^t]);
			}
		}
		spfa(s);
	}
	int ans=1e9;
	for(int i=1;i<=n;i++)
	{
		ans=min(ans,f[i][(1<<k)-1]);
	}
	printf("%d",ans);
	return 0;
}

Delov的旅行

https://www.gxyzoj.com/d/hzoj/p/P2876

最大值最小,考虑二分。

因为只能走两次,所以必须走完一个子树再走另一个。

\(f(x,y)\) 表示第一个走的叶子到 u 的距离为 x,最后一个为 y 的方案是否存在

对于 u 的两个子树,若 \(y_l+v_l+x_r+v_r\le mid\),就可以转移

考虑哪些状态是没有必要的,若 \(x1<x2\)\(y1<y2\),那么 \(f(x2,y2)\)就没有意义了

此时将有意义的状态按 x 升序排列,则 y 是降序,可以双指针维护

点击查看代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=132000;
int n,val[N][2],ch[N][2];
struct node{
	int x,y;
};
vector<node> v[N];
bool cmp(node x,node y)
{
	return x.x<y.x;
}
void dfs(int u,int x)
{
	v[u].clear();
	if(!ch[u][0])
	{
		v[u].push_back((node){0,0});
		return ;
	}
	if(ch[u][0]) dfs(ch[u][0],x);
	if(ch[u][1]) dfs(ch[u][1],x);
	vector<node> v1;
	v1.clear();
	for(int i=0;i<=1;i++)
	{
		int ls=ch[u][i],rs=ch[u][i^1],t=0;
		int tmp=x-val[u][0]-val[u][1];
		for(int j=0;j<v[ls].size();j++)
		{
			while(t+1<v[rs].size()&&v[rs][t+1].x<=tmp-v[ls][j].y) t++;
			if(t>=v[rs].size()||v[rs][t].x>tmp-v[ls][j].y) continue;
			v1.push_back((node){v[ls][j].x+val[u][i],v[rs][t].y+val[u][i^1]});
		}
	}
	sort(v1.begin(),v1.end(),cmp);
	for(int i=0;i<v1.size();i++)
	{
		if(v[u].size()&&v1[i].y>v[u].back().y) continue;
		v[u].push_back(v1[i]);
	}
}
bool check(int x)
{
	dfs(1,x);
	return v[1].size();
}
int main()
{
	freopen("trip.in","r",stdin);
	freopen("trip.out","w",stdout);
	scanf("%d",&n);
	for(int i=2;i<=n;i++)
	{
		int x,v;
		scanf("%d%d",&x,&v);
		if(!ch[x][0]) ch[x][0]=i,val[x][0]=v;
		else ch[x][1]=i,val[x][1]=v;
	}
	int l=1,r=1e9;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%d",l);
	return 0;
}

划分

https://www.gxyzoj.com/d/hzoj/p/4941

可以拆开看每个 1 的贡献,可以发现 1 所在位越靠前贡献越大,所以直接拆成一长段 + k-1 小段

此时若 x 和 y 贡献相同,则前 n-k 位必然相同,末尾是否为 1 不影响

然后二分哈希找到第一个不相同的位置,然后判断

特殊情况:

  1. 前面有超过 k-1 个 0,直接插板

  2. k=n,每个数一段 直接数

点击查看代码
#include<cstdio>
#include<string>
#include<iostream>
#define ull unsigned long long
#define ll long long
using namespace std;
const int N=2e6+5,p=131,mod=998244353;
int n,k,cnt,a[N];
ull h[N],p1[N];
string s;
ll fac[N],inv[N];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
ll C(int n,int m)
{
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
	scanf("%d%d",&n,&k);
	cin>>s;
	p1[0]=1;
	for(int i=1;i<=n;i++) p1[i]=p1[i-1]*p;
	for(int i=1;i<=n;i++)
	{
		a[i]=s[i-1]-'0';
		h[i]=h[i-1]*p+a[i];
//		printf("%d ",h[i]);
		cnt+=a[i];
	}
	if(n==k)
	{
		printf("%d 1",cnt);
		return 0;
	}
	int x=0;
	while(a[x+1]==0) x++;
	fac[0]=inv[0]=1;
	for(int i=1;i<=n;i++)
	{
		a[i]=s[i-1]-'0';
		fac[i]=fac[i-1]*i%mod; 
	}
	inv[n]=qpow(fac[n],mod-2);
	for(int i=n-1;i>0;i--) inv[i]=inv[i+1]*(i+1)%mod;
	if(x>=k-1)
	{
		ll res=0,sum=0;
		for(int i=k-1;i<=x;i++)
		{
			sum=(sum+C(x,i))%mod;
		}
		for(int i=1;i<=n;i++) res=(res*2+a[i])%mod;
		printf("%lld %lld",res,sum);
		return 0;
	}
	int t=n-k+1,mx=1,res=1;
//	printf("%d ",t);
	for(int i=2;i<=k;i++)
	{
		int l=0,r=t;
		while(l<r)
		{
			int mid=(l+r+1)>>1;
			if(h[i+mid-1]-h[i-1]*p1[mid]==h[mx+mid-1]-h[mx-1]*p1[mid]) l=mid;
			else r=mid-1;
		}
//		printf("%d ",l);
		l++;
		if(l>=t) res++;
		else if(a[i+l-1]>a[mx+l-1]) mx=i,res=1;
	}
	ll sum=0;
	for(int i=mx;i<=mx+t-1;i++) sum=(sum*2+a[i])%mod,cnt-=a[i];
	printf("%lld %d\n",(sum+cnt)%mod,res);
	return 0;
}

二分图最大权匹配

省流:模拟费用流

先考虑朴素做法

n 对个点两两匹配,会有 \(n^2\) 条边,考虑优化,距离公式为 \(|x1-x2|+|y1-y2|\),可以发现,如果去掉最大值,结果只小不大

所以可以建 4 个虚点,表示坐标对应的正负性,此时 \(O(n)\) 条边,\(n\le 1000\) 时暴力费用流即可

可以发现,每一条增广路一定是 S->左侧->中间->?->中间……->右侧->T,所以模拟这一过程,floyd乱搞就行。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
int n,p[2][N],typ[4][4];
int vx[4]={1,1,-1,-1};
int vy[4]={1,-1,1,-1};
ll w[2][4][N],dis[4][4];
priority_queue<pair<ll,int> > q1[2][4],q2[2][4][4];
vector<int> path[4][4];
bool check(int d,int x,priority_queue<pair<ll,int> > &q)
{
	while(!q.empty()&&p[d][q.top().second]!=x) q.pop();
	return !q.empty();
}
void update(int d,int x)
{
	int v=p[d][x];
//	printf("%d ",v);
	for(int i=0;i<4;i++)
	{
		if(i!=v&&!d) q2[0][v][i].push(make_pair(w[0][i][x]-w[0][v][x],x));
		if(i!=v&&d) q2[1][i][v].push(make_pair(w[1][i][x]-w[1][v][x],x));
//		printf("w%d %d %d\n",i,w[d][i][x]-w[d][v][x],x);
	}
}
ll MCMF()
{
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<4;j++)
		{
			path[i][j].clear();
			path[i][j].push_back(i);
			if(i==j)
			{
				dis[i][j]=0;
				continue;
			}
			path[i][j].push_back(j);
			dis[i][j]=-1e18;
			if(check(0,i,q2[0][i][j])&&q2[0][i][j].top().first>dis[i][j])
			{
				dis[i][j]=q2[0][i][j].top().first;
//				printf("c%d %d %lld\n",i,j,dis[i][j]);
				typ[i][j]=0;
			}
			if(check(1,j,q2[1][i][j])&&q2[1][i][j].top().first>dis[i][j])
			{
				dis[i][j]=q2[1][i][j].top().first;
//				printf("d%d %d %lld\n",i,j,dis[i][j]);
				typ[i][j]=1;
			}
		}
	}
	for(int k=0;k<4;k++)
	{
		for(int i=0;i<4;i++)
		{
			for(int j=0;j<4;j++)
			{
				if(dis[i][j]<dis[i][k]+dis[k][j])
				{
					dis[i][j]=dis[i][k]+dis[k][j];
					path[i][j]=path[i][k];
					path[i][j].pop_back();
					path[i][j].insert(path[i][j].end(),path[k][j].begin(),path[k][j].end());
				}
				
			}
		}
	}
	ll maxc=-1e18;
	int x=0,y=0;
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<4;j++)
		{
			if(check(0,-1,q1[0][i])&&check(1,-1,q1[1][j]))
			{
				ll t1=q1[0][i].top().first,t2=q1[1][j].top().first;
				if(maxc<t1+t2+dis[i][j]) maxc=t1+t2+dis[i][j],x=i,y=j;
			}
//			printf("a%d %d %lld\n",i,j,dis[i][j]);
		}
	}
//	printf("%d %d\n",x,y);
	if(x!=y)
	{
		for(int i=0;i<path[x][y].size()-1;i++)
		{
			int u=path[x][y][i],v=path[x][y][i+1],t=q2[typ[u][v]][u][v].top().second;
			if(!typ[u][v]) p[0][t]=v,update(0,t);
			else p[1][t]=u,update(1,t);
		}
	}
	int t=q1[0][x].top().second;
//	printf("%d ",t);
	p[0][t]=x;
	update(0,t);
	t=q1[1][y].top().second;
//	printf("%d\n",t);
	p[1][t]=y;
	update(1,t);
	return maxc;
}
int main()
{
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		ll x,y;
		scanf("%lld%lld",&x,&y);
		for(int j=0;j<4;j++) w[0][j][i]=x*vx[j]+y*vy[j];
		p[0][i]=p[1][i]=-1;
	}
	for(int i=1;i<=n;i++)
	{
		ll x,y;
		scanf("%lld%lld",&x,&y);
		for(int j=0;j<4;j++) w[1][j][i]=-x*vx[j]-y*vy[j];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<4;j++)
		{
			q1[0][j].push(make_pair(w[0][j][i],i));
			q1[1][j].push(make_pair(w[1][j][i],i));
//			printf("a%lld %lld\n",w[0][j][i],w[1][j][i]);
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++) ans+=MCMF();
	printf("%lld",ans);
	return 0;
}

距离(distance)

将 i 当成平面上的点 \((a_i,b_i)\),由 \(max(a,b)+min(a,b)=a+b\),可以将原式转化为两点的曼哈顿距离减切比雪夫距离

切比雪夫距离可以转化为曼哈顿距离,记:\(p_i=\frac{a_i+b_i}{2},q_i=\frac{a_i-b_i}{2}\),则 \(min(|a_i-a_j|,|b_i-b_j|)=|p_i-p_j|+|q_i-q_j|\)

所以此时开值域线段树启发式合并可以做到 \(O(nlog^2n)\),继续优化

对于一个有序数组,记中点为 m,则贡献为:

\(f_{1,m}+f_{m+1,n}+sum_{1,m}*cnt_{m+1,n}+sum_{m+1,n}*cnt_{1,m}\)

可以直接放到线段树上,合并时看两棵树之间的贡献,再加上子树的贡献即可,注意要做 4 次

点击查看代码
#include<cstdio>
using namespace std;
const int N=5e5+5,mod=1e9+7,inv=5e8+4;
int n,head[N],edgenum,tot,f[N],a[N],b[N],p[N],ans[N],tx,ty;
struct edge{
	int to,nxt;
}e[N*2];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int ls[N*33],rs[N*33],sum[N*33],cnt[N*33],rt[N];
void update(int &p,int l,int r,int v)
{
	p=++tot;
	ls[p]=rs[p]=sum[p]=cnt[p]=0;
	if(l==r)
	{
		sum[p]=v,cnt[p]=1;
		return;
	}
	int mid=(1ll*l+r)>>1;
	if(v<=mid) update(ls[p],l,mid,v);
	else update(rs[p],mid+1,r,v);
	sum[p]=(sum[ls[p]]+sum[rs[p]])%mod;
	cnt[p]=(cnt[ls[p]]+cnt[rs[p]])%mod;
}
int merge(int x,int y,int id)
{
	if(!x||!y) return x+y;
	f[id]=(f[id]+1ll*sum[rs[x]]*cnt[ls[y]]-1ll*sum[ls[x]]*cnt[rs[y]])%mod;
	f[id]=(f[id]+1ll*sum[rs[y]]*cnt[ls[x]]-1ll*sum[ls[y]]*cnt[rs[x]])%mod;
	sum[x]=(sum[x]+sum[y])%mod,cnt[x]=(cnt[x]+cnt[y])%mod;
	ls[x]=merge(ls[x],ls[y],id);
	rs[x]=merge(rs[x],rs[y],id);
	return x;
}
void dfs(int u,int fa)
{
	update(rt[u],tx,ty,p[u]);
	f[u]=0;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		f[u]=(f[u]+f[v])%mod;
		rt[u]=merge(rt[u],rt[v],u);
	}
}
int main()
{
	freopen("distance.in","r",stdin);
	freopen("distance.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a[i],&b[i]);
		p[i]=a[i];
	}
	tx=1,ty=1e9,dfs(1,0);
	for(int i=1;i<=n;i++) ans[i]=f[i]*2%mod,p[i]=b[i];
	tot=0,dfs(1,0);
	for(int i=1;i<=n;i++) ans[i]=(ans[i]+f[i]*2ll)%mod,p[i]=a[i]+b[i];
	ty=2e9,tot=0,dfs(1,0);
	for(int i=1;i<=n;i++) ans[i]=(ans[i]-f[i])%mod,p[i]=a[i]-b[i];
	tx=-1e9,ty=1e9,tot=0,dfs(1,0);
	for(int i=1;i<=n;i++) ans[i]=(ans[i]-f[i])%mod;
	for(int i=1;i<=n;i++)
	{
		printf("%d\n",(ans[i]+mod)%mod);
	}
	return 0;
}

团队选拔(selection)

人傻常数大

可以发现,对于一个点,以它结尾的 gcd 不同的段数最多为 \(log\ a_i\),所以可以每次扫一遍,将到 i 公约数相同的合并,这样会得到一些四元组 \((x,l,r,v)\),表示以 x 结尾,起点在 l,r 之间的区间,公约数为 v

对于相同的 v,记 \(f_i\) 表示最后一段右端点在 i 及 i 之前的方案数,则 \(f_x=f_{x-1}+\sum_{i=l-1}^{r-1} f_i\)

正序和倒序分别求一遍,容斥一下。

对于这个式子,就是区间修改区间查询,可以树状数组

可以发现,每次只有每个 x 的值发生改变,中间是不变的,所以可以统计答案时维护一个差分数组

点击查看代码
#include<cstdio>
#include<set>
#include<vector>
#include<algorithm>
#define IT set<int>::iterator
#define ll long long
#define lid id<<1
#define rid id<<1|1
using namespace std;
const int N=1e5+20,mod=998244353;
inline int gcd(int a,int b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
int n,a[N],l2[N],s[N],t[N],cnt1,cnt2,d[N];
int ans[N];
struct ST_table{
	int st[N][18];
	inline void build()
	{
		for(int i=1;i<=n;i++) st[i][0]=a[i];
		for(int j=1;j<=17;j++)
		{
			for(int i=1;i+(1<<j)-1<=n;i++)
			{
				st[i][j]=gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
			}
		}
	}
	inline int query(int l,int r)
	{
		int k=l2[r-l+1];
		return gcd(st[l][k],st[r-(1<<k)+1][k]);
	}
}ST;
set<int> st;
struct node{
	int x,l,r,v;
	bool operator <(const node &x)const{
		return v<x.v;
	}
};
vector<node> vl,vr;
struct segment_tree{
	ll a[N],b[N],c[N];
	int lowbit(int x)
	{
		return x & (-x);
	}
	void add(int x,int val)
	{
		int id=x;
		while(id<=n+5)
		{
			b[id]=(b[id]+val)%mod;
			c[id]=(c[id]+1ll*val*x)%mod;
			id+=lowbit(id);
		}
	}
	void update(int t,int l,int r,int v)
	{
		l++,r++;
		add(l,v),add(r+1,-v);
	}
	int getans(int x)
	{
		int ans=0;
		int id=x;
		while(id>0)
		{
			ans=(ans+1ll*b[id]*(x+1)-c[id])%mod;
			id-=lowbit(id);
		}
		return ans;
	}
	int query(int t,int l,int r)
	{
		l++,r++;
		return getans(r)-getans(l-1);
	}
}S,T;
vector<int> v;
inline bool cmp(node x,node y)
{
	if(x.v!=y.v) return x.v<y.v;
	return x.x<y.x;
}
inline int read()
{
	char ch=getchar();
	int x=0;
	while(ch<48||ch>57) ch=getchar();
	while(ch>=48&&ch<=57) x=x*10+ch-48,ch=getchar();
	return x;
}
inline void write(int x)
{
	if(x>=10) write(x/10);
	putchar(x%10+48);
}
int main()
{
//	freopen("1.txt","r",stdin);
//	freopen("2.txt","w",stdout);
	freopen("selection.in","r",stdin);
	freopen("selection.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		if(i!=1) l2[i]=l2[i>>1]+1;
	}
	ST.build();
	s[++cnt1]=0;
	for(int i=1;i<=n;i++)
	{
		t[cnt2=1]=0,s[++cnt1]=i;
		for(int j=2;j<=cnt1;j++) d[s[j]]=ST.query(s[j],i);
		for(int j=2;j<=cnt1;j++)
		{
			if(cnt2>=2&&d[t[cnt2]]==d[s[j]]) t[cnt2]=s[j];
			else t[++cnt2]=s[j];
		}
		for(int j=1;j<cnt2;j++)
		{
			s[j+1]=t[j+1];
			int x=d[t[j+1]];
			vl.push_back((node){i,t[j]+1,t[j+1],x});
//			printf("%d %d %d %d\n",i,t[j]+1,t[j+1],x);
			st.insert(x);
		}
		cnt1=cnt2;
	}
	s[cnt1=1]=n+1;
	for(int i=n;i>0;i--)
	{
		t[cnt2=1]=n+1,s[++cnt1]=i;
		for(int j=2;j<=cnt1;j++) d[s[j]]=ST.query(i,s[j]);
		for(int j=2;j<=cnt1;j++)
		{
			if(cnt2>=2&&d[t[cnt2]]==d[s[j]]) t[cnt2]=s[j];
			else t[++cnt2]=s[j];
		}
		for(int j=1;j<cnt2;j++)
		{
			s[j+1]=t[j+1];
			vr.push_back((node){i,t[j+1],t[j]-1,d[t[j+1]]});
//			printf("%d %d %d %d\n",i,t[j+1],t[j]-1,ST.query(i,t[j]-1));
		}
		cnt1=cnt2;
	}
	sort(vl.begin(),vl.end(),cmp),sort(vr.begin(),vr.end(),cmp);
	IT it=st.begin();
	S.update(1,0,n+1,1),T.update(1,0,n+1,1);
	while(it!=st.end())
	{
		v.clear();
		node tmp=(node){0,0,0,*it};
		int l1=lower_bound(vl.begin(),vl.end(),tmp)-vl.begin();
		int r1=upper_bound(vl.begin(),vl.end(),tmp)-vl.begin()-1;
		int l2=lower_bound(vr.begin(),vr.end(),tmp)-vr.begin();
		int r2=upper_bound(vr.begin(),vr.end(),tmp)-vr.begin()-1;
//		printf("%d %d %d\n",l1,r1,*it);
		for(int i=l1;i<=r1;i++)
		{
			int x=S.query(1,vl[i].l-1,vl[i].r-1);
//			printf("%d %d %d %d\n",vl[i].x,vl[i].l,vl[i].r,vl[i].v);
			S.update(1,vl[i].x,n,x);
			v.push_back(vl[i].x);
		}
		for(int i=r2;i>=l2;i--)
		{
			int x=T.query(1,vr[i].l+1,vr[i].r+1);
			T.update(1,1,vr[i].x,x);
			v.push_back(vr[i].x);
		}
		v.push_back(1),v.push_back(n);
		sort(v.begin(),v.end());
		int t=S.query(1,n,n);
		for(int i=0;i<v.size()-1;i++)
		{
			if(v[i]==v[i+1]) continue;
			int x=S.query(1,v[i],v[i]),y=T.query(1,v[i+1],v[i+1]);
			int t1=(t-1ll*x*y)%mod;
			ans[v[i]+1]=(ans[v[i]+1]+t1)%mod,ans[v[i+1]]=(ans[v[i+1]]-t1)%mod;
		}
		for(int i=0;i<v.size();i++)
		{
			if(i!=0&&v[i]==v[i-1]) continue;
			int x=S.query(1,v[i]-1,v[i]-1),y=T.query(1,v[i]+1,v[i]+1);
			int t1=(t-1ll*x*y)%mod;
			ans[v[i]]=(ans[v[i]]+t1)%mod,ans[v[i]+1]=(ans[v[i]+1]-t1)%mod;
		}
		for(int i=r1;i>=l1;i--)
		{
			int x=S.query(1,vl[i].l-1,vl[i].r-1);
			S.update(1,vl[i].x,n,-x);
		}
		for(int i=l2;i<=r2;i++)
		{
			int x=T.query(1,vr[i].l+1,vr[i].r+1);
			T.update(1,1,vr[i].x,-x);
		}
		it++;
	}
//	return 0;
	for(int i=1;i<=n;i++)
	{
		ans[i]=(ans[i-1]+ans[i])%mod;
		write((ans[i]+mod)%mod),putchar(' ');
	}
	return 0;
}

终末螺旋

设一个点能点亮的区间为 \([l,r]\),那么区间之间一定是包含或不交,按包含关系分段,次数就是段数,方案数就是每段只被一个区间包含的点数乘积

接下来考虑如何找这些段,显然每段的每种数字都恰好出现 2 次,考虑异或哈希,找到头尾相等点,区间 +1,哈希值为 0 就是大区间的划分点

接下来考虑修改,可以发现每次只改一个点,分为三种情况

  1. lst!=0,now!=0 段数不变,处理段内

  2. lst=0,now!=0 相当于合并两段

  3. lst!=0,now=0 相当于分裂两端

可以 set 维护每种哈希值的出现位置,线段树维护覆盖情况,区间修改,区间求最小值个数

点击查看代码
#include<bits/stdc++.h>
//#include<time.h>
#define ll long long
#define lid id<<1
#define rid id<<1|1
#define IT set<int>::iterator
using namespace std;
const int N=2e5+5,mod=998244353;
int n,q,cnt,a[N*2];
ll w[N*2],s[N*2],ans,inv[N*2];
mt19937_64 rnd(random_device{}());
inline ll get_rnd()
{
	ll x=0;
	while(!x) x=rnd();
	return x;
}
unordered_map<ll,int> mp;
multiset<int> st[N*5];
struct seg_tr{
	int l,r,cnt,mn,lazy;
}tr[N*8];
inline void build(int id,int l,int r)
{
	tr[id].l=l,tr[id].r=r,tr[id].cnt=r-l+1;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
}
inline void pushdown(int id)
{
	if(tr[id].lazy)
	{
		tr[lid].lazy+=tr[id].lazy;
		tr[lid].mn+=tr[id].lazy;
		tr[rid].lazy+=tr[id].lazy;
		tr[rid].mn+=tr[id].lazy;
		tr[id].lazy=0;
	}
}
inline void update(int id,int l,int r,int v)
{
	if(l>r) return;
	if(tr[id].l==l&&tr[id].r==r)
	{
		tr[id].mn+=v,tr[id].lazy+=v;
		return;
	}
	pushdown(id);
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid) update(lid,l,r,v);
	else if(l>mid) update(rid,l,r,v);
	else update(lid,l,mid,v),update(rid,mid+1,r,v);
	tr[id].mn=min(tr[lid].mn,tr[rid].mn),tr[id].cnt=0;
	if(tr[lid].mn==tr[id].mn) tr[id].cnt+=tr[lid].cnt;
	if(tr[rid].mn==tr[id].mn) tr[id].cnt+=tr[rid].cnt;
}
inline int query(int id,int l,int r)
{
	if(tr[id].l==l&&tr[id].r==r)
	{
		if(tr[id].mn==0) return tr[id].cnt;
		else return 0;
	}
	pushdown(id);
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid) return query(lid,l,r);
	else if(l>mid) return query(rid,l,r);
	else return query(lid,l,mid)+query(rid,mid+1,r);
}
inline ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
inline void add(IT x,int val)
{
	IT it=x,it1,it2;
	it1=--it,it2=++it;
	if(val==-1) ans=ans*inv[query(1,(*it1)+1,*it2)]%mod;
	else ans=ans*query(1,(*it1)+1,*it2)%mod;
//	printf("%d %d %d\n",x,val,ans);
}
inline void change(int id,ll x,ll y)
{
	if(!mp[y]) mp[y]=++cnt;
	int tx=mp[x],ty=mp[y];
	IT it=st[tx].lower_bound(id),it1,it2;
	IT it3=st[1].lower_bound(id);
//	printf("%lld %lld %d %d\n",x,y,ty,cnt);
	if(x==0)
	{
		it1=--it,it++,it2=++it,it--;
		ans=ans*inv[query(1,(*it1)+1,id)]%mod;
		ans=ans*inv[query(1,id+1,*it2)]%mod;
		ans=ans*query(1,(*it1)+1,*it2)%mod;
		st[tx].erase(it);
	}
	else
	{
		add(it3,-1);
		it1=st[tx].end();
		if(st[tx].size()>=2) update(1,(*st[tx].begin())+1,*(--it1),-1);
//		printf("%d\n",*it);
		st[tx].erase(it);
		it1=st[tx].end();
		if(st[tx].size()>=2) update(1,(*st[tx].begin())+1,*(--it1),1);
		add(it3,1);
	}
	it3=st[1].lower_bound(id);
	if(y==0)
	{
//	printf("a");
		st[ty].insert(id);
		it=st[ty].lower_bound(id);
		it1=--it,it++,it2=++it,it--;
		ans=ans*query(1,(*it1)+1,id)%mod;
		ans=ans*query(1,id+1,*it2)%mod;
		ans=ans*inv[query(1,(*it1)+1,*it2)]%mod;
	}
	else
	{
		add(it3,-1);
		it1=st[ty].end();
		if(st[ty].size()>=2) update(1,(*st[ty].begin())+1,*(--it1),-1);
		st[ty].insert(id),it1=st[ty].end();
		if(st[ty].size()>=2) update(1,(*st[ty].begin())+1,*(--it1),1);
		add(it3,1);
	}
	s[id]=y;
}
inline int read()
{
	int x=0;
	char ch=getchar();
	while(ch<48||ch>57) ch=getchar();
	while(ch>=48&&ch<=57) x=x*10+ch-48,ch=getchar();
	return x;
}
inline void write(int x)
{
	if(x>=10) write(x/10);
	putchar(x%10+48);
}
int main()
{
//	freopen("1.txt","r",stdin);
//	freopen("2.txt","w",stdout);
	freopen("tower.in","r",stdin);
	freopen("tower.out","w",stdout);
	n=read(),q=read();
	for(int i=1;i<=n;i++)
	{
		w[i]=get_rnd();
//		printf("%lld\n",inv[i]);
	}
	for(int i=1;i<=n*2;i++)
		a[i]=read();
	mp[0]=++cnt;
	st[cnt].insert(0);
	inv[0]=1;
//	double sta=clock();
	for(int i=1;i<=n*2;i++)
	{
		s[i]=s[i-1]^w[a[i]];
		int x=mp[s[i]];
		if(!x) x=mp[s[i]]=++cnt;
		st[x].insert(i);
		inv[i]=qpow(i,mod-2);
	}
//	double fin=clock();
//	printf("t%lf\n",fin-sta);
	build(1,1,n*2);
	for(int i=2;i<=cnt;i++)
	{
		IT it=st[i].begin(),it1;
		it1=++it,it--;
		while(it1!=st[i].end()&&it!=st[i].end())
		{
			update(1,(*it)+1,*it1,1);
			it++,it1++;
		}
	}
	IT it=st[1].begin(),it1;
	it1=++it,it--;
	ans=1;
	while(it1!=st[1].end()&&it!=st[1].end())
	{
		int tmp=query(1,*(it)+1,*it1);
//		printf("%d %d %d\n",(*it)+1,*it1,tmp);
		ans=(ans*tmp)%mod;
		it++,it1++;
	}
	write(st[1].size()-1),putchar(' '),write(ans),putchar('\n');
	while(q--)
	{
		int x=read();
//		printf("%d ",x);
		change(x,s[x],s[x-1]^w[a[x+1]]);
		swap(a[x],a[x+1]);
		write(st[1].size()-1),putchar(' '),write(ans),putchar('\n');
	}
	return 0;
}

酒吧

显然头尾必须选,所求是 \(\sum (b_{p_i}+b_{p_{i-1}})*(p_i-p_{i-1})\),类似于梯形面积

将原题每个点的贡献放到图上,则对应点为 \((i,b_i)\),最大面积就是一个凸包

软件工程

神秘分类,先观察样例发现,有一种相对优的情况就是选 k-1 个最长的,其他求交,但是有很多情况无法满足

如果区间 x 包含区间 y,那么 x 要么单开一个,要么和 y 在一组

所以考虑处理出所有不包含任何区间的区间,按左端点排序,易得,右端点单调

\(f_{i,j}\) 表示第 i 段,以 j 结尾 \(f_{i+1,k}=max(f_{i,j}+r_{j+1}-l_k)\),可以滚一维

点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=5005;
int n,k,cnt;
ll f[N],mx[N];
bool v[N];
struct node{
	int l,r;
}a[N],b[N];
bool cmp(node x,node y)
{
	return x.r-x.l>y.r-y.l;
}
bool cmp1(node x,node y)
{
	return x.l<y.l;
}
int main()
{
	freopen("se.in","r",stdin);
	freopen("se.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a[i].l,&a[i].r);
	}
	sort(a+1,a+n+1,cmp);
	ll s1=0;
	for(int i=1;i<k;i++) s1+=a[i].r-a[i].l;
	int tl=1,tr=1e6;
	for(int i=k;i<=n;i++) tl=max(tl,a[i].l),tr=min(tr,a[i].r);
	s1+=max(0,tr-tl);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i==j) continue;
			if(a[i].l<=a[j].l&&a[i].r>=a[j].r) v[i]=1;
		}
		if(!v[i]) b[++cnt]=a[i];
	}
	sort(b+1,b+cnt+1,cmp1);
	for(int i=1;i<=k;i++) mx[i]=-1e10;
	mx[0]=b[1].r;
//	printf("a");
	for(int i=1;i<=cnt;i++)
	{
		for(int j=1;j<=min(k,i);j++) f[j]=mx[j-1]-b[i].l;
		for(int j=1;j<=min(k,i);j++) mx[j]=max(f[j]+b[i+1].r,mx[j]);
	}
	for(int i=1;i<=n;i++)
	{
		if(!v[i]) continue;
		for(int j=k;j>1;j--) f[j]=max(f[j],f[j-1]+a[i].r-a[i].l);
	}
	printf("%lld",max(s1,f[k]));
	return 0;
}

填算符

显然所有与放在前面一定不劣,所以考虑先放好再考虑能否后移

首先考虑最后面的点,后移后一定是一段与+一段或+一个与+一段或

对于一段或,内部交换顺序不影响答案,所以可以 ST 表,求得的值和 ans 比对即可

接下来考虑下一位,它的位置一定在上一位之前,所以上一位后的一段或已经贡献到答案不会变

所以可以在最优答案中去掉这一部分的位,此时剩下的就是前面必须为 1 的二进制位,然后按新的右端点重复操作即可

点击查看代码
#include<cstdio>
#define ll long long
using namespace std;
const int N=1e6+5;
int n,k,l2[N],b[N];
ll a[N],ans,sum[N];
struct ST_table{
	ll st[N][21];
	void build()
	{
		for(int i=1;i<=n;i++) st[i][0]=a[i];
		for(int j=1;j<=20;j++)
		{
			for(int i=1;i+(1<<j)-1<=n;i++)
			{
				st[i][j]=st[i][j-1]|st[i+(1<<(j-1))][j-1];
			}
		}
	}
	ll query(int l,int r)
	{
		if(r<l) return 0;
		int k=l2[r-l+1];
		return st[l][k]|st[r-(1<<k)+1][k];
	}
}ST;
int get(int x,int r,ll v)
{
	b[x]=0;
	int i;
	for(i=r;i>=x;i--)
	{
		ll tmp=((sum[x]|ST.query(x+1,i))&a[i+1])|ST.query(i+2,r+1);
//		printf("a%d %d %lld %lld\n",x,i,tmp,v);
		if((tmp&v)==v) break;
	}
	b[i]=1;
	return i;
}
int main()
{
	freopen("bitop.in","r",stdin);
	freopen("bitop.out","w",stdout);
	scanf("%d%d",&n,&k);
	sum[0]=(1ll<<61)-1;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		sum[i]=sum[i-1]&a[i];
		if(i<=k) b[i]=1;
		if(i!=1) l2[i]=l2[i/2]+1;
	}
//	printf("a");
	ST.build();
	ans=sum[k+1]|ST.query(k+2,n);
//	printf("%lld ",ans);
	int r=n-1;
	ll res=ans;
	for(int i=k;i>0;i--)
	{
		int x=get(i,r,res);
//		printf("%d %d\n",i,x);
		if(x==i) break;
		ll tmp=ST.query(x+2,r+1);
		res=res&(res^tmp),r=x-1;
	}
	for(int i=1;i<n;i++)
	{
		if(b[i]) printf("%d ",i); 
	}
	return 0;
}

逆序图

可以发现连通块一定是一个连续段,如果存在一个散点 x 和一段 \([l,r](x>r)\) 为连通块,那么若值域连续,那么中间的一定更大,否则一定可以加上一些值在中间的点

\(f_{i,0}\) 表示 i 个数为一段的贡献,不保证联通,\(f_{i,1}\) 保证联通,\(g_i\) 表示 i 个为一段,保证联通的方案数

\(f_{i,0}\) 就是固定差,选两个位置形成逆序对,其他随便

\(f_{i,0}=\sum_{j=1}^{i-1} i*(i-1)/2*(i-j)*a_j*(i-2)!\)

\(g_i=i!-\sum_{j=1}^{i-1} g_j*(i-j)!\),即所有减去一个整块剩下随便的方案数

\(f_{i,1}=f_{i,0}-\sum_{j=1}^{i-1} j!f_{i-j,1}+f_{j,0}*g_{i-j}\),意义同上

\(ans_i=\sum_{j=0}^{i-1} ans_j*f_{i-j,1}+ans_j*g_{i-j}+j!*f_{i-j,1}\) 表示选且在序列中间,不选,和以它开头

点击查看代码
#include<cstdio>
#define ll long long
using namespace std;
const int N=5005,mod=998244353;
int n;
ll a[N],f[N][2],g[N],fac[N],ans[N];
int main()
{
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%lld",&a[i]);
	}
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	for(int i=1;i<=n;i++)
	{
		g[i]=fac[i];
		for(int j=1;j<i;j++)
		{
			f[i][0]=(f[i][0]+1ll*i*(i-1)/2*(i-j)%mod*a[j]%mod*fac[i-2])%mod;
			g[i]=(g[i]-g[i-j]*fac[j])%mod;
		}
//		printf("%lld %lld\n",f[i][0],g[i]);
	}
	for(int i=1;i<=n;i++)
	{
		f[i][1]=f[i][0];
		for(int j=1;j<i;j++)
		{
			f[i][1]=(f[i][1]-fac[j]*f[i-j][1]-f[j][0]*g[i-j])%mod;
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<i;j++)
		{
			ans[i]=(ans[i]+ans[j]*f[i-j][1]+ans[j]*g[i-j]+fac[j]*f[i-j][1])%mod;
		}
	}
	printf("%lld",ans[n]);
	return 0;
}

命运的X

可以当作每一刻有一个赌徒,拥有 1 元,每秒按照顺序赌下一秒出现的字符,赢则翻 m 倍,输则输光,全猜中结束,轮数为人数

显然这个游戏是公平的,人数=钱数,考虑最后的钱的情况,倒数第 n 个人有 \(m^n\) 元,前面的全部输光

考虑还有谁有钱,如果 x 开始时,倒数第 n 个人在赌 y,且 y 后面的串与 x 会完全重合,那么就会有钱,钱数为 m 的长度次方

所以可以找到这些串,算出钱数,就是期望长度

人口局 DBA

就是固定前缀,求解有多少组解满足 \(x_1+x_2+\cdots+x_a=s,x_i<m,x_1<p\)

忽略第二个限制,枚举至少有多少个数超过 m,然后插板 \(\sum_{i=1} (-1)^i C_a^i*C_{a-1}^{s-km+a-1}\)

然后加上限制,就是减去强制让 \(x_1\ge p\) 的方案数 \(\sum_{i=1} (-1)^i C_a^i*(C_{a-1}^{s-km+a-1}-C_{a-1}^{s-km+a-1-p})\)

点击查看代码
#include<cstdio>
#define ll long long
using namespace std;
const int N=2005,M=4e6+5,mod=1e9+7;
int m,l,sum[N],a[N];
ll fac[M],inv[M],ans;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
ll C(int n,int m)
{
	if(n<0||m<0||n<m) return 0;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
ll get(int s,int p,int k)
{
	ll res=0,op=-1;
	for(int i=0;i<k;i++)
	{
		op*=-1;
		res=(res+op*C(k-1,i)*(C(s-i*m+k-1,k-1)-C(s-i*m-p+k-1,k-1)))%mod;
	}
	return res;
}
int main()
{
	freopen("dba.in","r",stdin);
	freopen("dba.out","w",stdout);
	scanf("%d%d",&m,&l);
	for(int i=1;i<=l;i++)
	{
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	fac[0]=1;
	for(int i=1;i<=m*l;i++) fac[i]=fac[i-1]*i%mod;
	inv[m*l]=qpow(fac[m*l],mod-2);
	for(int i=m*l-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
	for(int i=1;i<=l;i++)
	{
		ans=(ans+get(sum[l]-sum[i-1],a[i],l-i+1))%mod;
	}
	printf("%lld",(ans+mod)%mod);
	return 0;
}

染色(color)

有两个要求,间隔一个数的两个数不同,奇偶性不同的两个数不同

然后可以发现,最小的合数为 4,没有任何质数为 4 的倍数

所以 4 个为一组,既满足了前面的要求,又不会有更小的方法

前缀

可以发现,如果当前构造的串的后缀与 S 的前缀最多匹配 k 位,则其他能配的一定是 border

这部分可以 kmp 处理 next 和贡献

正跑不好约束,考虑反过来,当前删了 i 个数,匹配后缀长度为 j,删除部分最大贡献 \(f_{i,j}\)

那么删掉一个数后,匹配长度为 j,可能从 j+1 转移,也可能从他的 next 转移,这部分可以前缀和

点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5005;
int n,m,a[N],ne[N],cnt[N],f[N][N];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	ne[1]=0;
	for(int i=2;i<=n;i++)
	{
		int j=ne[i-1];
		while(j&&a[i]!=a[j+1]) j=ne[j];
		if(a[i]==a[j+1]) j++;
		ne[i]=j;
	}
	cnt[0]=1,ne[0]=-1;
	for(int i=1;i<=n;i++)
	{
		int j=i;
		while(j!=-1)
		{
			if(a[i+1]==a[j+1]) cnt[i]++;
			j=ne[j];
		}
	}
	for(int i=1;i<=m;i++)
	{
		f[i][0]=f[i-1][1]+cnt[0];
		for(int j=1;j<=n;j++)
		{
			f[i][j]=max(f[i][ne[j]],f[i-1][j+1]+cnt[j]);
		}
	}
	printf("%d",f[m][0]);
	return 0;
}

摧毁

有一个东西叫最小割树,任意两点的最小割就是树上路径的最小值,所以只需要求 n-1 个点对之间的最小割

考虑如何加速求解,首先发现断掉一条外环上的边的代价高于断掉所有其他边的代价,所以考虑先断掉两个环边,然后断掉所有连接这两部分的边

可以将断边抽象成一些坐标,记编号较小的为 x,较大的为 y,位置 \((x,y)\) 上的值为断掉这两条边使得原图不联通的最小代价

可以发现,对于一条边 \((u,v)\),它只有一端在区间 内,一端在区间外时有贡献,所以只会在矩形 $ x\in(1, u − 1), y\in(u, v − 1)$ 以及 $x \in(u, v − 1), y \in(v, n) $ 中有贡献,查询同理

所以就可以用扫描线+可持久化线段树记录区间历史最小值,注意,同一个位置一定要先加正数,保证所取值一定是一个整行,而且要记录位置和时间

因为图的特殊性,所以假设当前未分割区间为 \([l,r]\),一定有一个分割点在区间内,一个在区间外,就不需要确定分割开的图,所以可以 \(O(nlog^2)\)

点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2.5e4+5,M=5e6+5,inf=1e9,mod=1e9+9;
int n,m,rt[N],s1[N],s2[N],cnt1,cnt2,cnt,f[N],siz[N];
ll c;
struct edge{
	int u,v,w;
}a[N],e[N];
struct opt{
	int x,l,r,w;
}x[N*4],y[N*4];
void add(int ax,int bx,int ay,int by,int w)
{
	if(ax>bx||ay>by) return;
	x[++cnt1]=(opt){ax,ay,by,w};
	x[++cnt1]=(opt){bx+1,ay,by,-w};
	y[++cnt2]=(opt){by,ax,bx,w};
	y[++cnt2]=(opt){ay-1,ax,bx,-w};
}
struct node{
	int t,x,mn;
	bool operator < (const node &a)const{
		return mn<a.mn;
	}
	node operator + (const node &a)const{
		return (node){a.t,x,a.mn+mn};
	}
};
struct segment_tree{
	int rt[N],ls[M],rs[M],idx;
	node ht[M],hv[M],tag[M],v[M];
	int copy(int id)
	{
		int p=++idx;
		tag[p]=tag[id],v[p]=v[id],ht[p]=ht[id],hv[p]=hv[id];
		ls[p]=ls[id],rs[p]=rs[id];
		return p;
	}
	void build(int &p,int l,int r)
	{
		p=++idx,v[p]=hv[p]=(node){0,l,inf},tag[p]=ht[p]=(node){0,0,0};
		if(l==r) return;
		int mid=(l+r)>>1;
		build(ls[p],l,mid);
		build(rs[p],mid+1,r);
	}
	void change(int p,node x,node h)
	{
		hv[p]=min(hv[p],v[p]+h);
		ht[p]=min(ht[p],tag[p]+h);
		v[p]=v[p]+x,tag[p]=tag[p]+x;
	}
	void pushdown(int p)
	{
		ls[p]=copy(ls[p]);
		rs[p]=copy(rs[p]);
		if(tag[p].t!=0)
		{
			change(ls[p],tag[p],ht[p]);
			change(rs[p],tag[p],ht[p]);
			tag[p]=ht[p]=(node){0,0,0};
		}
	}
	void update(int p,int l,int r,int ql,int qr,node x)
	{
//		if(tag[p].mn<-10) printf("%d %d %d %d %d %d\n",p,l,r,ql,qr,tag[p].mn);
		if(ql<=l&&qr>=r)
		{
			change(p,x,x);
//			printf("c%d %d %d\n",l,r,tag[p].t);
			return;
		}
		pushdown(p);
		int mid=(l+r)>>1;
		if(ql<=mid) update(ls[p],l,mid,ql,qr,x);
		if(qr>mid) update(rs[p],mid+1,r,ql,qr,x);
		v[p]=min(v[ls[p]],v[rs[p]]);
		hv[p]=min(hv[ls[p]],hv[rs[p]]);
//		printf("a%d %d %d %d\n",l,r,v[p].mn,hv[p].mn);
	}
	node query(int p,int l,int r,int ql,int qr,node t1,node t2)
	{
//		printf("q%d %d %d %d %d %d %d %d\n",l,r,ql,qr,t1.mn,t2.mn,v[p].mn,hv[p].mn);
		if(ql<=l&&qr>=r) return min(hv[p],v[p]+t2);
		int mid=(l+r)>>1;
		if(!t1.t) t1=tag[p],t2=ht[p];
		else t1=tag[p]+t1,t2=min(t2+tag[p],ht[p]);
		node res=(node){0,0,inf};
		if(ql<=mid) res=min(res,query(ls[p],l,mid,ql,qr,t1,t2));
		if(qr>mid) res=min(res,query(rs[p],mid+1,r,ql,qr,t1,t2));
		return res;
	}
}S,T;
bool cmp1(opt x,opt y)
{
	if(x.x==y.x) return x.w>y.w;
	else return x.x<y.x;
}
bool cmp2(opt x,opt y)
{
	if(x.x==y.x) return x.w>y.w;
	else return x.x>y.x;
}
node getval(int x,int y)
{
	node t1=(node){0,0,inf},t2=(node){0,0,inf};
	if(x!=1) t1=S.query(S.rt[x-1],1,n,x,y-1,(node){0,0,0},(node){0,0,0});
	t2=T.query(T.rt[y],1,n,x,y-1,(node){0,0,0},(node){0,0,0});
	swap(t2.x,t2.t);
	return min(t1,t2);
}
void build_tr(int l,int r)
{
	if(l==r) return;
	node tmp=getval(l,r);
	int mid=0;
	e[++cnt]=(edge){l,r,tmp.mn};
	if(tmp.t<r&&tmp.t>=l) mid=tmp.t;
	else mid=tmp.x;
	build_tr(l,mid),build_tr(mid+1,r);
}
int find(int x)
{
	if(f[x]!=x) f[x]=find(f[x]);
	return f[x];
}
bool cmp(edge a,edge b)
{
	return a.w>b.w;
}
int main()
{
	scanf("%d%d%lld",&n,&m,&c);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		if(u>v) swap(u,v);
		a[i]=(edge){u,v,w};
		add(1,u-1,u,v-1,w);
		add(u,v-1,v,n,w);
	}
	x[++cnt1]=(opt){1,1,n,-inf},y[++cnt2]=(opt){n,1,n,-inf};
	sort(x+1,x+cnt1+1,cmp1),sort(y+1,y+cnt2+1,cmp2);
	int p=1;
	S.build(S.rt[0],1,n),T.build(T.rt[n+1],1,n);
//	printf("a");
	for(int i=1;i<=n;i++)
	{
		S.rt[i]=S.copy(S.rt[i-1]);
		while(p<=cnt1&&x[p].x==i)
		{
//			printf("%d %d %d %d\n",x[p].x,x[p].l,x[p].r,x[p].w);
			S.update(S.rt[i],1,n,x[p].l,x[p].r,(node){i,0,x[p].w});
			p++;
		}
	}
	p=1;
//	printf("\n");
	for(int i=n;i>0;i--)
	{
		T.rt[i]=T.copy(T.rt[i+1]);
		while(p<=cnt2&&y[p].x==i)
		{
			T.update(T.rt[i],1,n,y[p].l,y[p].r,(node){i,0,y[p].w});
			p++;
		}
	}
	build_tr(1,n);
	sort(e+1,e+cnt+1,cmp);
	for(int i=1;i<=n;i++) f[i]=i,siz[i]=1;
	ll ans=0;
	for(int i=1;i<=cnt;i++)
	{
		int u=find(e[i].u),v=find(e[i].v);
		ans=(ans+1ll*siz[u]*siz[v]*e[i].w)%mod;
		f[u]=v,siz[v]+=siz[u];
	}
	ans=(ans*2+2ll*n*(n-1)*c)%mod;
	printf("%lld",ans);
	return 0;
}

飞翔的胖鸟

当 b=0 时,答案显然为 \(ah\)

\(g(i)=\sin i\),则 \(g'(i)=\dfrac{\sin (i+\Delta i)-\sin i}{\Delta i}\cos i\)

然后对原式求导:

\[\begin{array} f(x) &= \dfrac{\dfrac{ah}{\sin (x+\Delta x)}+b(x+\Delta x)-\dfrac{ah}{\sin x}-b(x)}{\Delta x} \\ &= \dfrac{-\dfrac{ah(\sin (x+\Delta x)-\sin x)}{\sin x\sin (x+\Delta x)}+b\Delta x}{\Delta x} \\ &= b-\dfrac{ah\cos x}{\sin^2 x}\end{array} \]

有公式 \(\sin^2 x+\cos^2 x=1\)\(p=\cos x\) 得到

\(f(x)=b-\dfrac{ahp}{1-p^2}\)

取到极值时斜率为 0,解方程即可

点击查看代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int Max=15707,mod=19260817;
int a,h,b,H,A,B;
void Read()
{
	long long x=1ll*h*H,y=1ll*a*A,z=1ll*b*B;
	h=(H^(y+z))%1000+1;
	a=(A^(x+z))%1000+1;
	b=(B^(x+y))%100000+1;
}
int T,typ,id;
int main()
{
	scanf("%d%d",&typ,&T);
	if(typ)
	{
		scanf("%d%d%d%d%d%d",&h,&a,&b,&H,&A,&B);
	}
	ll p=1,sum=0;
	for(int x=1;x<=T;x++)
	{
		if(!typ)
		{
			scanf("%d%d%d",&h,&a,&b);
		}
		else Read();
		double ans=0;
		if(b)
		{
			double delt=1.0*a*a*h*h+4.0*b*b;
			double x=(-1.0*a*h+sqrt(delt))/(2.0*b);
			double seta=acos(x);
//			printf("%.6lf\n",seta);
			ans=1.0*a*h/sin(seta)+b*seta;
		}
		else ans=a*h;
		p=p*11514%mod;
		if(typ)
		{
			ll k=ans*10000;
			sum=(sum+k%mod*p%mod)%mod;
		}
		else printf("%.4lf\n",ans);
	}
	if(typ) printf("%lld\n",sum);
	return 0;
}

刷墙

\(f_{i,j}\) 表示在区间 \([i,j]\) 中包含的区间最多能留多少种颜色

根据题意,每个点只有最后覆盖的颜色能留存,所以枚举这个点,任选一个跨过这个点的区间对他进行覆盖

此时区间就变成了两段,就可以区间 dp 转移,枚举断点即可

点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=605;
int n,l[N],r[N],a[N],cnt,sum[N][N],f[N][N];
int get(int ax,int bx,int ay,int by)
{
	return sum[bx][by]-sum[bx][ay-1]-sum[ax-1][by]+sum[ax-1][ay-1];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&l[i],&r[i]);
		a[++cnt]=l[i],a[++cnt]=r[i];
	}
	sort(a+1,a+cnt+1);
	cnt=unique(a+1,a+cnt+1)-a-1;
//	printf("%d ",cnt);
	for(int i=1;i<=n;i++)
	{
		l[i]=lower_bound(a+1,a+cnt+1,l[i])-a;
		r[i]=lower_bound(a+1,a+cnt+1,r[i])-a;
		sum[l[i]][r[i]]++;
	}
	for(int i=1;i<=cnt;i++)
	{
		for(int j=1;j<=cnt;j++)
		{
			sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
		}
	}
	for(int len=1;len<=cnt;len++)
	{
		for(int i=1;i+len-1<=cnt;i++)
		{
			int j=i+len-1;
			for(int k=i;k<j;k++)
			{
				f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+(get(i,k,k+1,j)>0));
			}
//			printf("%d %d %d\n",i,j,f[i][j]);
		}
	}
	printf("%d",f[1][cnt]);
	return 0;
}

重复

其实是哈希,可以发现,把 bc 及 ef 合在一起考虑,忽略 a,就是找到一个串,存在长度小于一半的 border

而 border 的长度都可以直接转移,设前缀起点为 i,后缀起点为 j,相等就可以直接从 (i+1,j+1) 转移

接下来加上 a,就是要满足一个前缀重复了两次,所以先记录以 i 为开头,j-1 为结尾是重复前缀是否满足条件

因为只需要满足 a 串的长度小于 b+c 串的长度,而 b+c 串的长度小于等于 border

所以要求一下不同长度的 a 串的贡献,其实就是做两次前缀和

点击查看代码
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
#define ull unsigned long long
#define ll long long
using namespace std;
const int N=5005,p=131;
ull h[N*2],p1[N];
int f[N][N],g[N][N],s1[N][N],n;
string s;
int main()
{
	cin>>s;
	p1[0]=1;
	n=s.size();
	for(int i=1;i<=n;i++)
	{
		h[i]=h[i-1]*p+s[i-1];
		p1[i]=p1[i-1]*p;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j*2-i-1<=n;j++)
		{
			if(h[j-1]-h[i-1]*p1[j-i]==h[j+j-i-1]-h[j-1]*p1[j-i]) f[i][j]=1;
//			printf("%d %d %d\n",i,j,f[i][j]);
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=i-1;j>0;j--)
		{
			f[j][i]+=f[j+1][i];
			s1[j][i]=s1[j+1][i]+f[j][i];
		}
	}
	ll sum=0;
	for(int i=n-1;i>0;i--)
	{
		for(int j=n;j>i;j--)
		{
			if(s[i-1]==s[j-1])
			g[i][j]=min(g[i+1][j+1]+1,j-i-1);
//			printf("%d %d %d\n",i,j,g[i][j]);
			if(g[i][j]>=2) sum+=s1[max(i-g[i][j]+1,1)][i]+f[1][i]*max(0,g[i][j]-i);
		}
	}
	printf("%lld",sum);
	return 0;
}

公交

先固定一个根,如果一个位置有路线经过,那么子树内就都可以走到这个点,减少的贡献为 \(siz_u\)

所以将 siz 视为边权,最后经过的就是 k 条长链

接下来考虑如何换根,考虑什么地方的权值会改变,子树内不变,首先要删去本身的贡献,然后考虑子树外

分为两部分,分别是父子树外和父子树内,但是因为这两部分等价,所以可以把第一部分直接在 dfs 前挂在父亲上

因为父亲的长链不能经过当前点,所以还要记一个次长链,此时只需加上父亲的贡献,就是总 siz 减去当前子树的 siz

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define IT multiset<ll>::iterator
using namespace std;
const int N=2e5+5;
int n,k,edgenum,head[N],a[N];
struct edge{
	int to,nxt;
}e[N*2];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
ll sum,siz[N],dis[N],fi[N],se[N],res,ans[N],d[N];
multiset<ll> s,t;
void dfs(int u,int fa)
{
	siz[u]=a[u];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		dis[u]+=dis[v]+siz[v];
		siz[u]+=siz[v];
		if(fi[v]+siz[v]>=fi[u])
		{
			se[u]=fi[u];
			fi[u]=fi[v]+siz[v];
			s.insert(se[u]);
//			printf("i%lld ",se[u]);
		}
		else
		{
			s.insert(fi[v]+siz[v]);
//			printf("i%lld ",fi[v]+siz[v]);
			se[u]=max(se[u],fi[v]+siz[v]);
		}
	}
}
void add(ll x)
{
	if(!x) return;
	if(t.size()&&x>=*t.begin()) t.insert(x),res+=x;
	else s.insert(x);
	if(t.size()>k)
	{
		res-=*t.begin(),s.insert(*t.begin());
		t.erase(t.begin());
	}
}
void del(ll x)
{
	if(!x) return;
//	printf("%lld %lld\n",x,*t.begin());
	if(t.size()&&x>=*t.begin()) t.erase(t.lower_bound(x)),res-=x;
	else s.erase(s.lower_bound(x));
	if(t.size()<k&&s.size())
	{
		IT it=s.end();
		it--;
		t.insert(*it),res+=(*it);
		s.erase(it);
	}
}
void dfs1(int u,int fa)
{
	if(u!=1)
	{
		d[u]=d[fa]+sum+dis[fa]-dis[u]-siz[u]*2;
	}
	ans[u]=d[u]+dis[u]-res;
//	printf("%d %lld %lld %lld %lld %lld\n",u,d[u],dis[u],res,fi[u],se[u]);
//	printf("%d %d\n",u,fa);
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		ll t1=fi[v],t2=se[v],tmp=0;
		add(fi[v]);
//		printf("%d %d %d\n",u,fa,v);
		del(fi[v]+siz[v]);
		if(fi[v]+siz[v]==fi[u])
		{
			del(se[u]),add(se[u]+sum-siz[v]);
			tmp=se[u]+sum-siz[v];
		}
		else
		{
			del(fi[u]),add(fi[u]+sum-siz[v]);
			tmp=fi[u]+sum-siz[v];
		}
//		if(v==2) printf("a%lld\n",tmp);
		if(tmp>fi[v]) se[v]=fi[v],fi[v]=tmp;
		else se[v]=max(se[v],tmp);
		dfs1(v,u);
		se[v]=t2,fi[v]=t1;
		if(fi[v]+siz[v]==fi[u])
		{
			add(se[u]),del(se[u]+sum-siz[v]);
		}
		else
		{
			add(fi[u]),del(fi[u]+sum-siz[v]);
		}
		del(fi[v]),add(fi[v]+siz[v]);
	}
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	s.insert(fi[1]);
	for(int i=1;i<=k;i++)
	{
		if(!s.size()) break;
		IT it=s.end();
		it--;
		t.insert(*it),res+=(*it);
		s.erase(it);
	}
//	printf("a");
	dfs1(1,0);
	for(int i=1;i<=n;i++)
	{
		printf("%lld ",ans[i]);
	}
	return 0;
}
posted @ 2025-08-25 21:23  wangsiqi2010916  阅读(16)  评论(0)    收藏  举报