训练周记#3

Day1

上 分 大 胜 利
感觉不久就能上2100了(兴奋地搓手手)
化学作业好多

Day2

EDU场没打 睡觉了 今天补了题

set答案是最大值减最小值-相邻两个最大的差,维护原数组和差分数组,每次更改找到前驱后继的两个区间改一下就行了。
奥妙计数
首先给 d d d数组排序,对于一个 b [ i ] b[i] b[i] 假设有 k k k个数大于等于他,那么在第 k k k个比他大的数后面才会有伤害。对于比 b [ i ] b[i] b[i]大的数,他有 1 − a [ i ] k 1-\frac{a[i]}{k} 1ka[i]的概率造成伤害,对于比他小的数,考虑 k k k个数之间有 k + 1 k+1 k+1个空,在第 a + 1 a+1 a+1个空以后的才会造成伤害,也就是 1 − a [ i ] + 1 k + 1 1-\frac{a[i]+1}{k+1} 1k+1a[i]+1的概率,再乘上和就是期望。
其实也可以大力组合数学算出总方案数,除以 n ! n! n!,可惜式子推得我有点晕。

#include
#define int long long
#define N 300015
#define mod 998244353
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=n;i>=a;i--)
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define lowbit(i) ((i)&(-i))
#define VI vector
using namespace std;
int n,m,d[N],a[N],b[N],pre[N],suf[N];
int qpow(int a,int b){
	int res = 1;
	while(b){
		if(b&1) res = (res*a)%mod;
		a = a*a%mod;
		b >>= 1;
	}
	return res;
}


signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
 	scanf("%lld%lld",&n,&m);
 	rep(i,1,n) scanf("%lld",&d[i]);
 	rep(i,1,m) scanf("%lld%lld",&a[i],&b[i]);
 	sort(d+1,d+n+1); 
 	rep(i,1,n) pre[i] = pre[i-1]+d[i]%mod;
 	per(i,1,n) suf[i] = suf[i+1]+d[i]%mod;
 	rep(i,1,m){
 		int pos = lower_bound(d+1,d+n+1,b[i])-d;
 		//cout << pos << endl;
 		int k = n-pos+1;
 		if(a[i] > k || a[i] == n){
 			//puts("fuck");
 			printf("0\n");
 		}else{
 			int pr = ((pre[pos-1]*(k-a[i]+1)%mod)%mod*qpow(k+1,mod-2)%mod)%mod,su = ((suf[pos]*(k-a[i])%mod)%mod*qpow(k,mod-2)%mod)%mod;
 			//cout <
 			printf("%lld\n", (pr+su)%mod);
 		}
 	}
	return 0;
}

Day3

线段树 / 神必哈希
首先讲线段树做法,我们设 o [ i ] [ j ] o[i][j] o[i][j]表示值为 i i i的数第 j j j次出现的位置, p o s [ a [ i ] ] pos[a[i]] pos[a[i]]表示当前是第几次出现,显然我们每次合法区间分为两种

  1. 不选: o [ a [ i ] ] [ p o s [ a [ i ] ] − > o [ a [ i ] ] [ p o s [ a [ i ] ] + 1 ] − 1 o[a[i]][pos[a[i]] -> o[a[i]][pos[a[i]]+1]-1 o[a[i]][pos[a[i]]>o[a[i]][pos[a[i]]+1]1
  2. 选: o [ a [ i ] ] [ p o s [ a [ i ] + 3 ] − > o [ a [ i ] ] [ p o s [ a [ i ] ] + 4 ] − 1 o[a[i]][pos[a[i]+3] -> o[a[i]][pos[a[i]]+4]-1 o[a[i]][pos[a[i]+3]>o[a[i]][pos[a[i]]+4]1

我们要找到对 n n n个数都合法的区间,用线段树维护区间最大值 m x [ i ] mx[i] mx[i]和最大值的个数 c n t [ i ] cnt[i] cnt[i],如果 m x [ 1 ] = = n mx[1] == n mx[1]==n,答案加上 c n t [ 1 ] cnt[1] cnt[1],每次往后移的时候把前一位赋值成 − i n f -inf inf
z y y zyy zyy的代码看了二十分钟才看明白,而 z y y zyy zyy只花了八分钟就做出了这道题。

#include
#define ll long long
#define N 500015
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=n;i>=a;i--)
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define lowbit(i) ((i)&(-i))
#define VI vector
#define ls (p<<1)
#define rs (p<<1|1)
using namespace std;
int n,a[N],pos[N];
VI o[N];
namespace seg{
	int lazy[N<<2],mx[N<<2],cnt[N<<2];
	void build(int p,int l,int r){
		mx[p] = 0;cnt[p] = r-l+1;
		if(l==r) return;
		int mid = (l+r)>>1;
		build(ls,l,mid);
		build(rs,mid+1,r);
	}
	void change(int p,int l,int r,int x,int y,int v){
		if(x <= l&&r <= y){
			lazy[p] += v;
			mx[p] += v;
			return;
		}
		int mid = (l+r)>>1;
		if(x <= mid) change(ls,l,mid,x,y,v);
		if(y > mid)  change(rs,mid+1,r,x,y,v);
		mx[p] = max(mx[ls],mx[rs]);cnt[p] = 0;
		if(mx[p] == mx[ls]) cnt[p] += cnt[ls];
		if(mx[p] == mx[rs]) cnt[p] += cnt[rs];
		mx[p] += lazy[p];
	}
}
using namespace seg;
void change(int x,int v){
	change(1,0,n,o[x][pos[x]],o[x][pos[x]+1]-1,v);
	if(pos[x]+3 < o[x].size()-1)
		change(1,0,n,o[x][pos[x]+3],o[x][pos[x]+4]-1,v);
}
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
 	scanf("%d",&n);
 	rep(i,1,n) scanf("%d",&a[i]);
 	rep(i,1,n) o[i].pb(0);
 	rep(i,1,n) o[a[i]].pb(i);
 	rep(i,1,n) o[i].pb(n+1);
 	build(1,0,n);
 	rep(i,1,n) change(i,1);
 	ll ans = 0;
 	rep(i,1,n){
 		change(1,0,n,i-1,i-1,-inf);
 		if(mx[1] == n) ans += cnt[1];
 		change(a[i],-1);
 		pos[a[i]]++;
 		change(a[i],1);
 	}
 	printf("%lld\n",ans);
	return 0;
}

哈希做法:
我们对每一个前缀求一个数组 p [ i ] = p [ i ] 1 , p [ i ] 2 , . . . , p [ i ] n p[i] = {p[i]_1,p[i]_2,...,p[i]_n} p[i]=p[i]1,p[i]2,...,p[i]n,其中 p [ x ] i p[x]_i p[x]i表示前 x x x个中 i i i出现了几次,对 3 3 3取模。
显然如果 p [ i ] = = p [ j ] p[i] == p[j] p[i]==p[j]那么这个区间显然是合法的,考虑到题目要求恰好三个而非三的倍数个,我们进行双指针,维护 p [ i ] p[i] p[i]。可是如果直接维护数组会出大问题,所以我们对数组求哈希,可以直接滚动哈希,但还有种神必 t r i c k trick trick,我们对每个 i i i求一个 l o n g   l o n g long \ long long long范围内的随机数 r [ i ] r[i] r[i],那么 h a s h [ x ] = ∑ i p [ x ] i ∗ r [ i ] hash[x] = \sum_{i}p[x]_i*r[i] hash[x]=ip[x]ir[i],碰撞概率是 1 2 63 \frac{1}{2^{63}} 2631

关于随机哈希,暑假集训时有道题有同样的 t r i c k trick trick
哈希
我们可以对每个数求一个随机数 r [ i ] r[i] r[i],维护区间随机数的异或值,如果区间异或值等于广义排列的异或值,则正确。

#include
#define ll long long
#define N 1000015
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=n;i>=a;i--)
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define lowbit(i) ((i)&(-i))
#define VI vector
using namespace std;
ll sum[N];
unsigned ll c[N],f[N],pre[N]; 
int n,m,q,a[N];
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	srand(time(0));
	scanf("%d%d",&n,&m);
	rep(i,1,n) scanf("%d",&a[i]);
	rep(i,1,m) f[i] = rand()*rand(),pre[i]=pre[i-1]^f[i];
	rep(i,1,n) for(int x=i;x<=n;x+=lowbit(x)) sum[x]+=a[i],c[x]^=f[a[i]];
	scanf("%d",&q);
	while(q--){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		if(u==2){
			for(int x=v;x<=n;x+=lowbit(x)) sum[x]+=w-a[v],c[x]^=f[a[v]]^f[w];
			a[v]=w;
		}else{
			ll s=0;unsigned ll msk=0;
			int len = w-v+1;
			for(int x=w;x;x-=lowbit(x)) s+=sum[x],msk^=c[x];
			for(int x=v-1;x;x-=lowbit(x)) s-=sum[x],msk^=c[x];
			s<<=1;
			int fi=(s/len+1-len)/2,la=fi+len-1;
			if(msk==(pre[la]^pre[fi-1])) puts("YES");
			else puts("NO");
		}
	}
	return 0;
}

更普遍的做法是维护区间和,区间平方和。

Day4

Gugugu

Day5

物理课

Day6

学了一天文化课,晚上MO考试三道大题只会一道/kk
cf狂掉五十分

Day7

上午睡大觉,下午队友带飞ccpc。
最后只有五题做出来了。

1002

对于每个质数,和 2 2 2连边最优,对于合数,和一个因子连边最优。
答案就是质数和*2+合数和。
m i n _ 25 min\_25 min_25筛求出 1 1 1~ n n n的质数和。

1005

s g sg sg函数,把每个数的指数和异或起来就是答案,因子 2 2 2的指数最多算一次。
队友打了一发 p o l l a r d _ r o u pollard\_rou pollard_rou挂了,赛后发现暴力分解就能过/kk

你可能感兴趣的:(训练周记#3)