自嗨测试赛3

任意模数快速插值

分治,讨论最大值在左边/左右相等/在右边,且最小值在左边/左右相等/在右边,

注意最大,最小值在异侧的情况,可以先按最大值,将另一侧跑到极限,然后差分出满足最小值在另一侧的最小值之和

Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;

const int maxn = 5e5+10;
const int mod = 998244353;
int n, ans;
int a[maxn], mx[maxn], mn[maxn], p[maxn], sum[maxn];

int read(int x = 0, bool f = 0, char ch = getchar()) {
	for(;ch < '0' || ch > '9';ch = getchar()) f = ch=='-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = (x<<3)+(x<<1)+(ch&15);
	return f ? -x : x;
}

void solve(int l, int r) {
	if(l == r) return (ans += (ll)a[l]*a[l]%mod)%=mod, void();
	int mid = (l+r)/2;
	solve(l, mid), solve(mid+1, r);
	mx[mid]=mn[mid]=a[mid], mx[mid+1]=mn[mid+1]=a[mid+1];
	for(int i = mid-1;i >= l; --i) mx[i]=max(mx[i+1], a[i]), mn[i]=min(mn[i+1], a[i]);
	for(int i = mid+2;i <= r; ++i) mx[i]=max(mx[i-1], a[i]), mn[i]=min(mn[i-1], a[i]);
	for(int i=mid, j=mid+1;i >= l; --i) { // (x1+x2)*(y1+y2)
		while(j<=r && mx[i]>=mx[j] && mn[i]<=mn[j]) ++j;
		(ans += (ll)mx[i]*mn[i]%mod*(j-mid-1)%mod)%=mod;
	}
	for(int j=mid+1, i=mid;j <= r; ++j) { // x3*y3
		while(i>=l && mx[i]<mx[j] && mn[i]>mn[j]) --i;
		(ans += (ll)mx[j]*mn[j]%mod*(mid-i)%mod)%=mod;
	}
	for(int i=mid, j=mid+1;i >= l; --i) {
		while(j<=r && mn[i]<=mn[j]) ++j;
		p[i]=j;
	}
	sum[mid]=0;
	for(int i=mid, j=mid+1;i >= l; --i) { // (x1+x2)*y3
		while(j<=r && mx[i]>=mx[j]) sum[j]=(sum[j-1]+mn[j])%mod, ++j;
		if(j-1 >= p[i]) (ans += (ll)mx[i]*(sum[j-1]-sum[p[i]-1])%mod)%=mod;
	}
	for(int j=mid+1, i=mid;j <= r; ++j) {
		while(i>=l && mn[i]>mn[j]) --i;
		p[j]=i;
	}
	sum[mid+1]=0;
	for(int j=mid+1, i=mid;j <= r; ++j) { // x3*(y1+y2)
		while(i>=l && mx[i]<mx[j]) sum[i]=(sum[i+1]+mn[i])%mod, --i;
		if(i+1 <= p[j]) (ans += (ll)mx[j]*(sum[i+1]-sum[p[j]+1])%mod)%=mod;
	}
}

int main() {
	freopen("chazhi.in","r",stdin);
	freopen("chazhi.out","w",stdout);
	n = read();
	for(int i = 1;i <= n; ++i) a[i] = read();
	solve(1, n);
	printf("%d
", (ans%mod+mod)%mod);
	return 0;
}

斗转星移

离线,将询问按时间排序,多维护几个标记,按优先级更新。

顺90度:(x,y) -> (y,-x)
逆90度:(x,y) -> (-y,x)
关于x=z对称:(x,y) -> ((2*z-x,y))
关于y=z对称:(x,y) -> ((x,2*z-y))

维护三个标记:1.交换标记 2.取负标记 3.增加标记

改一个标记的时候,按优先级依次更新就好了

Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;

const int maxn = 1e6+10;
int n, m, q, op[maxn], z[maxn];
ll x[maxn], y[maxn], ansx[maxn], ansy[maxn], Add[2];
bool Fan[2], Swp;
struct Node {
	int cnt, x, id;
	bool operator < (const Node &B) const {
		return cnt < B.cnt;
	}
}a[maxn];

int read(int x = 0, bool f = 0, char ch = getchar()) {
	for(;ch < '0' || ch > '9';ch = getchar()) f = ch=='-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = (x<<3)+(x<<1)+(ch&15);
	return f ? -x : x;
}

void push_swap() {
	swap(Add[0], Add[1]);
	swap(Fan[0], Fan[1]);
}

int main() {
	freopen("passing.in","r",stdin);
	freopen("passing.out","w",stdout);
	n=read();
	for(int i=1;i <= n; ++i) x[i]=read(), y[i]=read();
	m=read();
	for(int i=1;i <= m; ++i) {
		op[i]=read();
		if(op[i]==3||op[i]==4) z[i]=read();
	}
	q=read();
	for(int i=1;i <= q; ++i) a[i]=(Node){read(), read(), i};
	sort(a+1, a+1+q);
	for(int i=1, now=1;i <= m; ++i) {
		if(op[i]==1) Swp^=1, push_swap(), Fan[1]^=1, Add[1]=-Add[1];
		if(op[i]==2) Swp^=1, push_swap(), Fan[0]^=1, Add[0]=-Add[0];
		if(op[i]==3) {
			Fan[0]^=1;
			Add[0]=-Add[0]+2ll*z[i];
		}
		if(op[i]==4) {
			Fan[1]^=1;
			Add[1]=-Add[1]+2ll*z[i];
		}
		while(now<=q && a[now].cnt==i) {
			ll xx=x[a[now].x], yy=y[a[now].x];
			if(Swp) swap(xx, yy);
			if(Fan[0]) xx=-xx;
			if(Fan[1]) yy=-yy;
			xx+=Add[0], yy+=Add[1];
			ansx[a[now].id]=xx;
			ansy[a[now].id]=yy;
			++now;
		}
	}
	for(int i=1;i <= q; ++i) printf("%lld %lld
", ansx[i], ansy[i]);
	return 0;
}

如何更快出题

高维前缀和+Boruvka最小生成树

加入n+1号点,点权为0,将所有朋友连边得到了一张联通图,点为x和y则边权为a[x]+a[y],答案就是最大生成树权值-所有点权值之和。

不难分析出这一定是合法的:

  • 最终的树上与n+1相连的点就是主动加入团队的

  • y被x连进树内,正好贡献a[x],以后被y连进来的,正好贡献a[y]

然后因为边数是n^2级别的,考虑Boruvka最小生成树,对于每次增广,用高位前缀和VlogV(V是值域)预处理,对于每个联通块,O(1)找到连向其他联通块的最优边。

预处理出f1[v],f2[v],表示满足以下三个条件的点x,y的编号

  • x,y不在同一联通块

  • a[x],a[y]都是v的子集

  • a[x],a[y]分别是满足条件1,2中的点的最大,次大

这样对于枚举点i,最优的出边连向的j一定是f1[maxs^a[i]],f2[maxs^a[i]]中的一个,因此对于每个点都能O(1)找到最优出边

Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn=2e5+10;
int n, cnt, mx, maxs=1, bit;
int a[maxn], fa[maxn], bl[maxn], f1[1<<20|10], f2[1<<20|10], tw[maxn], ta[maxn];
ll ans;

int read(int x=0, bool f=0, char ch=getchar()) {
	for(;ch<'0' || ch>'9';ch=getchar()) f=ch=='-';
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch&15);
	return f?-x:x;
}

int findrt(int x) {
	return fa[x]==x?x:fa[x]=findrt(fa[x]);
}

void modify(int x, int id) {
	if(id==-1) return;
	int v=a[id], c=bl[id];
	if(f1[x]==-1) f1[x]=id;
	else if(a[f1[x]]<=v) {
		if(f1[x]!=-1&&bl[f1[x]]!=c) f2[x]=f1[x];
		f1[x]=id;
	}
	else if(f2[x]==-1&&c!=bl[f1[x]]) f2[x]=id;
	else if(a[f2[x]]<=v&&c!=bl[f1[x]]) f2[x]=id;
}

int main() {	
	freopen("threat.in","r",stdin);
	freopen("threat.out","w",stdout);
	n=read();
	for(int i=1;i<=n;++i) mx=max(mx, a[i]=read()), ans-=a[i];
	while(maxs<=mx) maxs<<=1, ++bit;
	--maxs, ++n;
	for(int i=1;i<=n;++i) fa[i]=i;
	while(cnt<n-1) {
		memset(tw, -1, sizeof tw);
		memset(f1, -1, sizeof(f1));
		memset(f2, -1, sizeof(f2));
		for(int i=1;i<=n;++i) {
			bl[i]=findrt(i);
			modify(a[i], i);
		}
		for(int i=0;i<=bit;++i) {
			for(int j=0;j<=maxs;++j) {
				if(j&(1<<i)) {
					modify(j, f1[j^(1<<i)]);
					modify(j, f2[j^(1<<i)]);
				}
			}
		}
		for(int i=1;i<=n;++i) {
			int v=(maxs^a[i]);
			int x=bl[i];
			if(f1[v]!=-1&&bl[f1[v]]!=x) {
				if(a[i]+a[f1[v]]>tw[x]) tw[x]=a[i]+a[f1[v]], ta[x]=bl[f1[v]];
			}
			else if(f2[v]!=-1&&bl[f2[v]]!=x) {
				if(a[i]+a[f2[v]]>tw[x]) tw[x]=a[i]+a[f2[v]], ta[x]=bl[f2[v]];
			}
		}
		for(int i=1;i<=n;++i) {
			if(findrt(i)!=i) continue;
			int x=findrt(ta[i]);
			if(x==i) continue;
			ans+=tw[i], ++cnt, fa[i]=x;
		}
	}
	printf("%lld
", ans);
	return 0;
}

原文地址:https://www.cnblogs.com/Lour688/p/14507961.html