• 【更新中】2021ZR模拟赛要题记录


    ZR模拟赛要题记录

    (NOIP十连测、考前二十连测)

    姊妹篇:ZR模拟赛未了的心愿

    NOIP_Day5_合并石子(Continuing)

    将长度都为 n 的 a 和 b 两列石子等概率一一配对求和,求第 k 大期望的石子个数。

    求第 K 大的期望,即求出对于任意 (x), 第 K 大的数等于 x 的期望,求和即为答案。

    对于每一个 x 来说:

    为了求出第 K 大等于 x,我们设:

    f_i 表示至少有 i 对小于等于 x 的方案数,
    g_i 表示恰好有 i 对小于等于 x 的方案数。

    根据二项式反演,有:

    g_i = sum_{j_i} ^ {j <= n} f_j * inom{j}{i}

    第 K 大的数小于等于 x $Leftrightarrow $ 存在某个 (k leq K), 恰好有 k - 1 个数大于 x ,其他的数都小于等于x。

    dp_{i,j} 表示匹配了a的前i个数,至少存在j对和小于等于x

    20连测_Day1_多项式题

    设d_i表示前i位的划分权值和,也就是字串[1,i]的答案,相当于从i和i+1之间断开。

    发现这种乘积加和满足乘法分配律,也就是对于划分出来的数字[i,j],(d_j = d_{i - 1} * num_{i,j}),我们并不关心前j位是怎么划分的,而可以直接把(num_{i,j})乘到后面。

    (d_i = sum_{j = 1} ^ {j < i} d_j * num_{j + 1, i} = sum_{j = 1} ^ {j < i} (d_j * (mulum[i] - mulum[j] * 10^{i - j})) = sum(d_j * mulum[i] - d_j * mulum[j] * 10^{i - j}))

    倒序枚举 j 那么(10^{i-j})可以累乘。

    前缀和优化, 记:
    ( D_i = sum_{j = 1} ^ {j <= i} d_i \ Sj_i = sum_{j = 1} ^ {j <= i} mulum[j] * Teninv[j] * d[j] )

    那么 (d_i = mulum[i] * D_{i - 1} - Ten[i] * Sj_{i - 1})

    复杂度:(O(n))

    int n;
    ll d[maxn], mulum[maxn], Ten[maxn], D[maxn], Sj[maxn], Teninv[maxn], inv10;
    char s[maxn];
    ll ksm(ll bs, int B){
    	ll res(1);
    	while(B){
    		if(B & 1){
    			res = res * bs % Mod;
    		}
    		bs = bs * bs % Mod;
    		B >>= 1;
    	}
    	return res;
    }
    int main(){
    	n = rd();
    	scanf("%s", s + 1);
    	Teninv[0] = D[0] = Ten[0] = d[0] = 1ll; inv10 = ksm(10, Mod - 2);	
    	for(int i(1); i <= n; ++i) Ten[i] = Ten[i - 1] * 10 % Mod, Teninv[i] = Teninv[i - 1] * inv10 % Mod, mulum[i] = (mulum[i - 1] * 10 + s[i] - '0') % Mod;
    	for(int i(1); i <= n; ++i){
    		d[i] = (D[i - 1] * mulum[i] % Mod - Sj[i - 1] * Ten[i] % Mod + Mod) % Mod;
    		D[i] = D[i - 1] + d[i]; Sj[i] = Sj[i - 1] + mulum[i] * Teninv[i] % Mod * d[i] % Mod;
    		if(D[i] >= Mod) D[i] -= Mod; if(Sj[i] >= Mod) Sj[i] -= Mod; 
    	}
    	printf("%lld
    ", d[n]);
    	return 0;
    }
    
    

    20连测_Day2_数集

    你需要维护⼀个⼀开始为空的⾮负整数集 S ,⽀持两种操作:

    1. 向集合 S 种加⼊⼀个数 x;
    2. 对于⼀个数字 y ,查询(max_{xin S} x op y) ,其中 op 是与、或、异或三种运算中的某⼏种。
      (x 值域是 (1 ^ 20)
    • op = xor

    用 trie 树维护集合即可。

    • op = and

    从高位向低位,如果 y 当前位为1,那么我们希望选择这一位为1的数字;

    如果 y 当前位为0,无法做出决定。
    每当我们遇到无法做出决定的时刻,就不做出决定,也即默认这一位为0。

    • op = or

    从高位向低位,如果 y 当前位为0,那么我们希望选择这一位为1的数字;
    如果 y 当前位为1,无法做出决定,只能为0。

    枚举子集

    枚举所有的正整数 i ,使得 (i | x = x),方法如下:

    for(int i = x; i; i = (i - 1) & x);
    

    注意这里的边界条件是 i != 0, 而不是 i >= 0,如果 i = 0 了, 那么负数做 & 运算会出大问题。

    每插入一个数 x, 我们把 vis[] 中 x 的所有子集 i 都置为1,代表与集合 S 内与 y 的按位与、按位或结果可能是 i。

    注意: trie 树要开值域乘2,因为 trie 树上第 i 层有 (2 ^ i) 个结点,(sum_{i=0}^{20} = 2 ^ 21 - 1)

    代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int rd(){
    	int res = 0, fl = 1; char c = getchar();
        while(!isdigit(c)){if(c == '-') fl = -1; c = getchar();}
        while(isdigit(c)){res = (res << 3) + (res << 1) + c - '0'; c = getchar();}
        return res * fl;
    }
    const int maxn = 1050010;
    int ans1, ans2, q, op, x, trie[2][maxn * 20], cnt, vis[maxn];
    int Max(int A, int B){
    	if(A < B) return B;
    	return A;
    }
    void insert(int num){
    	int k = 0;
    	for(int i(20); i >= 0; --i){
    		int b1 = (num >> i) & 1;
    		if(!trie[b1][k]) trie[b1][k] = ++cnt;
    		k = trie[b1][k];
    	}
    } 
    int queryxor(int num){
    	int k(0), res(0);
    	for(int i(20); i >= 0; --i){
    		int b1 = (num >> i) & 1;
    		if(trie[!b1][k]){
    			res += (1 << i);
    			k = trie[!b1][k];
    		}
    		else k = trie[b1][k];
    	}
    	return res;
    }
    int main(){
    //	freopen("B2.in", "r", stdin);
    	q = rd();
    	for(int i(1); i <= q; ++i){
    		op = rd(), x = rd();
    		if(op == 1){
    			insert(x); 
    			if(!vis[x])
    				for(int i(x); i; i = (i - 1) & x)	vis[i] = 1;
    			continue; 
    		}
    		if(op == 3){
    			printf("%d
    ", queryxor(x)); continue;
    		}
    		ans1 = ans2 = 0;
    		for(int i(20); i >= 0; --i){
    			if(x & (1 << i)){//当前为1,&希望为1,| 无所谓 
    				if(vis[ans1 | (1 << i)]) ans1 |= (1 << i);
    			}
    			//当前为0,& 无所谓,|希望为1 
    			else if(vis[ans2 | (1 << i)]) ans2 |= (1 << i);
    		}
    		printf("%d %d %d
    ", queryxor(x), ans1, ans2 | x);
    	}
    	return 0;
    }
    

    20连测_Day2_染色

    20连测_Day3_A

    预处理 popcount

    我的方法:

    for(int i(1);i<len;++i){pct[i]=pct[i-lowbit(i)]+1;}
    

    老师的方法:

    for(int x(1);x<=n;++x) popct[x] = popct[x >> 1] + (x & 1);
    

    内存过大,数组访问不连续,不如O(n)跑函数

    以下写法在 n=25,m=291 的极限数据情况下,运行时间超过了2000ms。

    unsigned pct[35000000];
    inline void dfs(unsigned pos,unsigned cnt,unsigned sta){
    	if(pos==n){++ans[cnt]; return;}
    	dfs(pos+1,cnt,sta);
    	dfs(pos+1,cnt+pct[sta&G[pos]],sta|(1<<pos));
    }
    int main(){
    	...
    	for(unsigned i(1);i<(1<<n);++i) pct[i]=pct[i>>1]+(i&1);
    	dfs(0,0,0);
    	...
    }
    

    而以下写法同样的数据只花费了324ms。

    inline int pct(unsigned x){
    	unsigned res(0);
    	for(;x;x-=lowbit(x)) res++;
    	return res;
    }
    inline void dfs(unsigned pos,unsigned cnt,unsigned sta){
    	if(pos==n){++ans[cnt]; return;}
    	dfs(pos+1,cnt,sta);
    	dfs(pos+1,cnt+pct(sta&G[pos]),sta|(1<<pos));
    }
    int main(){
    	dfs(0,0,0);
    }
    

    这是因为 pct 数组的大小有3.5e7,内存访问不连续,所以慢到理论上的(O(1))查询还不如写一个函数来 (O(n)) 求。

    20连测_Day3_B[莫反板题]

    (sum_{i=l}^{i<=r}[gcd(a_i,x)==1] \= sum_{i=l}^{i<=r}sum_{d|gcd(a_i,x)}mu(d) \= sum_{i=l}^{i<=r}sum_{d|x,d|d_i}mu(d) \= sum_{d|x}sum_{i=l}^{i<=r}[d|a_i]*mu(d))

    这时候的神仙操作就是先给所有询问按照 r 排序,然后总的均摊 O(n) 处理所有 r 前面的 a[i]。

    更神仙的是对于左端点 l ,只需要给 l-1 乘一个 -1 的系数,转化为 r,一并排序,然后在最终输出答案的时候再把 r 得到的答案减去 l 得到的答案即可。

    对于 (d|a_i) 相当于是我们要找一个数 d 的倍数的出现次数。

    那么处理a[i]的操作就是(O(sqrt(a)))算出他是那些数的倍数即可。

    (mu(x)=1 if (x == 1);\=(-1)^k if (x 无平方因数且 x=Pi_{i=1}^{i<=k}p_i)\0 else)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int rd(){
    	int res = 0, fl = 1; char c = getchar();
        while(!isdigit(c)){if(c == '-') fl = -1; c = getchar();}
        while(isdigit(c)){res = (res << 3) + (res << 1) + c - '0'; c = getchar();}
        return res * fl;
    }
    const int maxn = 100010;
    int mu[maxn];
    unsigned not_pri[maxn], pri[maxn], top, q, n, m, l, r, x, a[maxn], cnt[maxn], ans[maxn];
    unsigned sum[1010][maxn];
    map<pair<int, int>, int> Cnt;
    int Gcd(int A, int B){
    	return B ? Gcd(B, A % B) : A;
    }
    int cal(int x){
    	int i, ans(0);
    	for(i=1;i*i<x;++i){
    		if(!(x%i)){
    			ans+=mu[i]*cnt[i], ans+=mu[x/i]*cnt[x/i];
    		} 
    	} 
    	if(i*i==x){
    		ans+=mu[i]*cnt[i];
    	} 
    	return ans;
    }
    void getmu(){
    	mu[1]=1;
    	for(int i(2);i<=100000;++i){
    		if(!not_pri[i]){
    			pri[++top]=i; mu[i]=-1;
    		}
    		for(int j(1);j<=top&&i*pri[j]<=100000;++j){
    			not_pri[i*pri[j]]=1;
    			if(i%pri[j]==0){
    				mu[i*pri[j]]=0;break;
    			}
    			mu[i*pri[j]]=-mu[i];
    		}
    	}
    }
    struct Query{
    	int r,x,id,t;
    	Query(){}
    	Query(int R,int X,int Id,int T){
    		r=R,x=X,id=Id,t=T;
    	}
    	bool operator < (Query Q) const {
    		return this->r < Q.r;
    	}
    }qry[maxn*2];
    int main(){
    	getmu();
    	n = rd(),m = rd();
    	for(int i(1); i <= n; ++i) a[i]=rd();
    	for(int i(1); i <= m; ++i){
    		l=rd()-1,r=rd(),x=rd();
    		qry[++q]=Query(l,x,i,-1), qry[++q]=Query(r,x,i,1);
    	}
    	sort(qry+1,qry+1+q);
    	int c1(1);
    	for(int i(0);i<=n;++i){
    		int x;
    		for(x=1;x*x<a[i];++x){
    			if(!(a[i]%x)) cnt[x]++,cnt[a[i]/x]++;
    		}
    		if(x*x==a[i]) cnt[x]++;
    		while(c1<=q&&qry[c1].r<=i){
    			ans[qry[c1].id]+=qry[c1].t*cal(qry[c1].x);
    			c1++;
    		}
    	}
    	for(int i=1;i<=m;++i) printf("%d
    ",ans[i]); printf("
    ");
    	return 0;
    }
    //sqrt(100000)=316 3e7
    //pri_100000 = 1e4
    //1e5*1e4/64=1.6e7 
    

    NOIp_Day6_买

    我在NOIp模拟赛中做小学奥数原题爆零了

    一次买一个,慢死。

    一次买 ( m a) 个直到钱不够 ( m a * x),有点慢。

    ( m a) 个得到 (b) 元钱,实际花费 ( m (a*x-b)) 元钱,如果 (a * x - b <= 0),我就可以无限买。

    (n/(a*x-b)) 表示买多少个 (a),但是当我的钱不够买 ( m a) 个但是却能花费 ( m a * x - b) 时是不能买的,所以先买 ((n-a*x)/(a*x-b)) 个。【1】

    可以确定的是

    那么现在我还有 (n=(n-a*x)mod(a*x-b)+a*x) 元钱,考虑我肯定至少还能买一开始保底的 (a) 个,而且还有剩钱。

    (rest=(n-a*x)mod(a*x-b)) 在 $$

    我能否买 (2*a) 个? (3*a) 个呢? 说不定可以,我买 (a) 还能增加 ( m b) 元钱,加上我剩的钱,我还能进行一波【1】操作。

    最后剩的钱绝对凑不够 (a * x) 就只能单买了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int rd(){
    	int res = 0, fl = 1; char c = getchar();
        while(!isdigit(c)){if(c == '-') fl = -1; c = getchar();}
        while(isdigit(c)){res = (res << 3) + (res << 1) + c - '0'; c = getchar();}
        return res * fl;
    }
    int T;
    ll ans, a, b, x, n, money;
    int main(){
    	T=rd();
    	for(int t(1);t<=T;++t){
    		ans=0;n=rd(),x=rd(),a=rd(),b=rd();
    		if(n<x){
    			printf("0
    "); continue;
    		}
    		if(n>=x*a&&a*x<=b){
    			printf("-1
    "); continue;
    		}
    		if(b==0){
    			ans=n/x;
    			printf("%lld
    ",ans); continue;
    		}
    		if(n<a*x){
    			printf("%lld
    ",n/x); continue;
    		}
    		ll l=(n-a*x)/(a*x-b);
    		money=n-l*(a*x-b);
    		ll l_=money%(a*x);
    		ll l__=l_/(a*x-b)+1;
    		money-=l__*(a*x-b);
    		ans=l*a+l__*a+money/x;
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    /*
    5
    100000000 2 1 1
    1000000000 100 2 99
    6651238 99 2 186
    100000000 100000000 2 100000001
    594580363 1 249195783 241474592
    */
    

    NOIp_Day7_C

    (设 f(x)=sum_{d|x} (mu(frac{x}{d}) * d))

    (那么 gcd(x, y)=sum_{d|x,d|y} f(d))

    (gcd(x,y)=sum_{d|x,d|y}(sum_{g|d}(mu(frac{d}{g}) * g)))

    证明:

    不会.

    枚举 ( m j), 枚举 (a_j) 的因子 ( m d), 这样对于每一个 ( m i), 如果 (a_i)( m d) 这个因子就会对答案有贡献.

    记录 (f[d] = sum_{g|d} mu(frac{d}{g}) * g, O(nsqrt{n}))

    维护 (sum[d]=sum_{i leq j And d|a[i] And d|a[j]} i)

    维护 (squm[d]=sum_{i leq j And d|a[i] And d|a[j]} i*i)

    Cal() 计算(sum_{i}( (i,j) 被包含的区间数量))

    [(i-1)*(i-2)/2+i-1 + [(n-j)*(n-j-1)/2+n-j]ast cnt + [(j-i-1)*(j-i-2)/2+j-i-1] = i*i/2-i/2 + [n*(n-1)/2+(1-j-n*2)*j/2]*cnt + j*j/2-i*j+i*i/2-3*j/2+3*i/2+1+j-i-1 = i*i + j*j/2 - i*j - (1/2+cnt/2)*j - n*cnt*j - 1 ]

    Ans += Cal(j, d) * f[d];

    20连测_Day9_B_操作数列

    给出一个长度为 (k) 的数列,然后给出 (n) 个操作,操作分为三种:

    1. a[i]=b
    2. a[i]+=b
    3. a[i]*=b
      其中 (i,b) 是给定的,每个操作只能用一次,最多使用 (m) 个操作,让整个数列的乘积最大。

    实际操作

    • 覆盖操作:对于每一个 (a[i]) 最多进行一次覆盖操作,也就是那个最大的 (b)
    • 增加操作:增加操作一定是在覆盖操作之后进行的,不然就白覆盖了。优先选择 (b) 大的加操作
    • 乘操作:乘操作一定放在加操作之后,优先选择 (b) 大的乘操作

    思考实际上的操作过程,对于每一个 (a_i),一开始可能有一次覆盖操作,也可能没有,这要看覆盖操作自己的本身,接下来进行的是一些 (b) 单调不增的加操作,最后是一些 (b) 单调不增的乘操作。

    连续!连续!前缀!前缀!

    由于最终的答案很大,所以没法按照一次操作结束后 (a) 的值进行排序,但是按照操作后的数与操作前的数的比值进行排序是完全没问题的。
    (Pi=Pi_{i=1}^{k} a_i),考虑进行一次乘操作之后,(Pi) 要乘上 (b)
    进行一次加操作后,(Pi) 变成 (Pi+b),相当于乘上了 (frac{Pi+b}{Pi})
    我们考虑把覆盖转化为加操作,覆盖 (b) 变成加 (b-a)

    这样对 (a_i) 的三种操作产生的实际效果都转化成为了对 (Pi) 的乘法。

    乘标记——实际操作的提炼与转化

    考虑所有的加操作(包括由覆盖操作转化过来的),按照 (b) 递减排序,从前往后扫,维护 (sum_i)(sum_i) 一开始等于 (a_i) 那么当前操作的乘标记即为 (frac{sum_i+b}{sum_i}),然后把 (b) 累加到 (sum_i) 里面。

    原先乘法的乘标记就是 (b)

    之所以我们能够记录一个 (sum_i) ,都基于操作的单调性和连续性,一旦我们进行了当前操作,意味着差值比当前操作大的那些操作一定会进行。

    贪心

    排序,选乘标记大于1的前 (m) 个。

    #include<bits/stdc++.h>
    #define Mod 1000000007
    using namespace std;
    typedef long long ll;
    int rd(){
    	int res = 0, fl = 1; char c = getchar();
        while(!isdigit(c)){if(c == '-') fl = -1; c = getchar();}
        while(isdigit(c)){res = (res << 3) + (res << 1) + c - '0'; c = getchar();}
        return res * fl;
    }
    const int maxn = 1000010;
    struct Add{
    	double Tmp;
    	ll del;
    	int id;
    }add[maxn];
    struct MUL{
    	ll Mul;
    	int id;
    }mul[maxn];
    int k, n, m, T, I, tot, top, cnt, Tot;
    ll a[maxn], cov[maxn], Sum[maxn], ans(1), B;
    bool CmpAdd(Add A, Add B){
    	return A.del>B.del;
    }
    bool CmpTmp(Add A, Add B){
    	return A.Tmp>B.Tmp;
    }
    struct Op{
    	double Tmp;
    	int id, tp, pos;
    }Ans[maxn];
    bool operator < (Op A, Op B){
    	return A.Tmp < B.Tmp;
    }
    priority_queue<Op> Q;
    int main(){
    	k=rd(), n=rd(), m=rd();
    	for(int i(1);i<=k;++i) a[i]=rd();
    	for(int i(1);i<=n;++i){
    		T=rd(),I=rd(),B=rd();
    		if(T==1) cov[I]=max(cov[I],B);
    		else if(T==2) add[++tot].del=B, add[tot].id=I;
    		else mul[++top]={B,I};
    	}
    	for(int i(1);i<=top;++i) Q.push(Op{mul[i].Mul,mul[i].id,3,i});
    	for(int i(1);i<=k;++i) if(cov[i]){
    		add[++tot].del=cov[i]-a[i], add[tot].id=i;
    	}
    	sort(add+1,add+1+tot,CmpAdd);
    	for(int i(1);i<=k;++i) Sum[i]=a[i]; 
    	for(int i(1);i<=tot;++i){
    		add[i].Tmp=1.0*(add[i].del+Sum[add[i].id])/Sum[add[i].id];
    		Q.push(Op{add[i].Tmp, add[i].id, 2, i}); 
    		Sum[add[i].id]+=add[i].del;
    	}
    	while(Q.size()){
    		Op u=Q.top(); Q.pop();
    		if(u.Tmp <= 1) break;
    		cnt++;
    		Ans[++Tot]=u;
    		if(cnt>=m) break; 
    	}
    	for(int i(1);i<=Tot;++i){
    		if(Ans[i].tp==2){
    			a[Ans[i].id]=(a[Ans[i].id]+add[Ans[i].pos].del)%Mod;
    		}
    	}
    	for(int i(1);i<=Tot;++i){
    		if(Ans[i].tp==3){
    			a[Ans[i].id]=a[Ans[i].id]*mul[Ans[i].pos].Mul%Mod;
    		}
    	}
    	for(int i(1);i<=k;++i) ans=ans*a[i]%Mod;
    	printf("%lld
    ", ans);
    	return 0;
    }
    
  • 相关阅读:
    [LeetCode] 1268. Search Suggestions System
    [LeetCode] 907. Sum of Subarray Minimums
    [LeetCode] 2034. Stock Price Fluctuation
    [LeetCode] 792. Number of Matching Subsequences
    [LeetCode] 212. Word Search II
    [LeetCode] 828. Count Unique Characters of All Substrings of a Given String
    [LeetCode] 408. Valid Word Abbreviation
    [LeetCode] 1161. Maximum Level Sum of a Binary Tree
    [LeetCode] 1152. Analyze User Website Visit Pattern
    [LeetCode] 636. Exclusive Time of Functions
  • 原文地址:https://www.cnblogs.com/ZhengkunJia/p/15472512.html
Copyright © 2020-2023  润新知