zoukankan      html  css  js  c++  java
  • NOI Online 2021 补题

    P7468 [NOI Online 2021 提高组] 愤怒的小 N

    P7469 [NOI Online 2021 提高组] 积木小赛

    P7470 [NOI Online 2021 提高组] 岛屿探险

    考场经历

    开场看一遍题,认定 T2 是签到, T1 很可做并且做出来就可以拉开差距,T3 一脸不会,毒瘤DS。事实上大致是准的,只不过 T3 想难了。

    准备光速写个 T2,T1 打完去 T3 骗分。

    果然 T2 很快敲完。但是不知道为什么只敲了 70,脑抽了。后来才反应过来 100 非常简单,码掉之后开 T1。但是因为CCF爆炸没交上去/tuu

    T1 随便分析一下就是一个裸的多项式平移,(O(nk^2))。想了想决定先码掉,结果整场考试都在调这玩意,,,

    游记结束。

    T1 到底是怎么了呢?

    我输出了 dp 值之后发现从第 (k) 项开始都相同,想着必然是我预处理dp那块挂了,死命调。

    直到考试结束最后 5min,想着还是要打点暴力,于是写了个暴力,发现 (n) 只有一个 (1) 都是对的!

    原来是我统计答案忘记多项式平移了!!/tuu

    然后再用那个性质,从第 (k) 项开始相同,直接就切了/tuu

    这 T1 不比你 T3 简单?怎么在洛谷 T1 是黑的,T3 是紫的???

    多给我两小时我可以 210+,这不 rk 前十/xia。看样子即使是 OI 赛制,手速也是非常重要的!


    T1 愤怒的小 N

    (a)(0)(b)(1)

    可以发现,长度为 (2^i) 的序列,右半边 (1) 就是左边 (0) 下标集体加 (2^{i-1}) 得到的,就是一个多项式平移(本质是二项式定理展开)。

    考虑直接维护 (a_i) 的系数。

    (g(i,k,j)(kin {0,1})) 表示长度为 (2^i) 的序列,(k) 所在下标构成的多项式,(a_j) 的系数。

    不妨把 (g(i,k)) 看做一个多项式,定义函数 (operatorname{TRANS}(F(x),p)) 表示 (F(x+p))

    有转移:

    [g(i,k)=g(i-1,k)+operatorname{TRANS}(g(i-1,kigoplus 1),2^{i-1}) ]

    把后半部分展开。

    注意到 (g) 其实是由很多项式加起来得到的,推的时候应该展开再合并。

    假设有值的位置有 (q) 个,分别为 (x_1,x_2,cdots,x_q)

    那么 (operatorname{TRANS}) 就是把

    [F(x)=sum_{j=0}^{k-1}a_jg_j\ =sum_{i=1}^{q}sum_{j=0}^{k-1}a_jx_i^j ]

    变成

    [F(x+p)=sum_{i=1}^{q}sum_{j=0}^{k-1}a_j(x_i+p)^j\ =sum_{j=0}^{k-1}a_jsum_{i=1}^{q}sum_{l=0}^{j}x_i^{j-l}p^{l}inom{j}{l}\ =sum_{j=0}^{k-1}a_jsum_{l=0}^{j}g_{j-l}p^linom{j}{l} ]

    可以 (O(k^2)) 转移。

    统计答案的时候应该从高位往低位跑,维护一个 now 表示最高 (i) 位构成的二进制数的值,维护一个 op 表示当前 (1) 的个数。

    可以发现答案是 (sum_{n_i=1}operatorname{TRANS}(g(i,op\% 2),now)a_i)

    这就是一个非常简单的 (O(nk^2)) 解法,MTT 一下可以 (O(nklog k))

    至于正解,就是很容易观察到 (g(i,k))(ige k) 的时候,(g(i,0)=g(i,1))

    那么考虑 (<k) 的部分 (O(k^3)) 暴力 (dp)(ge k) 的部分拉格朗日插值,求 (f) 的前缀和然后除以 (2) 即可。

    组合数千万不要用阶乘 因为我一开始 (nk^2) 的时候用的阶乘懒得改,就被卡常了

    发现被卡常了/tuu

    翻了翻题解,发现可以用一个 ull 存,每 (16) 次取一次模,取模次数大大减少,就过去了。(感谢 _Enthalphy 的题解)。

    复杂度 (O(log n+k^3))

    Code
    #include<bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define mkp(x,y) make_pair(x,y)
    #define pb(x) push_back(x)
    #define sz(v) (int)v.size()
    typedef long long LL;
    typedef double db;
    template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
    template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
    #define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
    #define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    	return f?x:-x;
    }
    
    #define mod 1000000007
    inline int qpow(int n, int k) {
    	int res = 1;
    	for(; k; k >>= 1, n = 1ll * n * n % mod)
    		if(k & 1) res = 1ll * n * res % mod;
    	return res;
    }
    inline void fmod(int &x) { x -= mod, x += x >> 31 & mod; }
    
    const int N = 500005;
    const int K = 505;
    int n, k, a[K], pw2[N], ans, dp[K][2][K], C[K][K];
    char S[N];
    inline void init() {
    	pw2[0] = 1;
    	rep(i, 1, n) pw2[i] = (pw2[i - 1] << 1) % mod;
    	C[0][0] = 1;
    	rep(i, 1, k) {
    		C[i][0] = 1;
    		rep(j, 1, i) fmod(C[i][j] = C[i - 1][j] + C[i - 1][j - 1]);
    	}
    }
    inline int lagrange(int X, int k, int *a) {
    	static int x[K], y[K];
    	for(int i = 0, s = 0; i <= k; ++i) {
    		x[i] = i, y[i] = s;
    		int tmp = 0;
    		for(int j = 0, o = 1; j < k; ++j, o = 1ll * o * i % mod)
    			fmod(tmp += 1ll * o * a[j] % mod);
    		fmod(s += tmp);
    	}
    	int res = 0;
    	for(int i = 0; i <= k; ++i) {
    		int A = y[i], B = 1;
    		for(int j = 0; j <= k; ++j) if(i != j)
    			A = 1ll * A * (X - x[j] + mod) % mod,
    			B = 1ll * B * (x[i] - x[j] + mod) % mod;
    		fmod(res += 1ll * A * qpow(B, mod - 2) % mod);
    	}
    	return res;
    }
    signed main() {
    	scanf("%s%d", S, &k), n = strlen(S), init();
    	for(int i = 0; i < k; ++i) a[i] = read();
    	dp[0][0][0] = 1;
    	for(int i = 1; i < min(n, k); ++i) {
    		for(int j = 0; j < k; ++j) {
    			unsigned long long r0 = 0, r1 = 0;
    			for(int l = 0, o = 1; l <= j; ++l, o = 1ll * o * pw2[i - 1] % mod) {
    				int t = 1ll * C[j][l]* o % mod;
    				r0 += 1ull * dp[i - 1][0][j - l] * t;
    				r1 += 1ull * dp[i - 1][1][j - l] * t;
    				if(!(l & 15)) r0 %= mod, r1 %= mod;
    			}
    			dp[i][0][j] = (r1 + dp[i - 1][0][j]) % mod;
    			dp[i][1][j] = (r0 + dp[i - 1][1][j]) % mod;
    		}
    	}
    	reverse(S, S + n);
    	for(int i = n - 1, op = 0, now = 0; i >= 0; --i) {
    		if(S[i] == '1') {
    			op ^= 1;
    			if(i < k) {
    				for(int j = 0; j < k; ++j) {
    					unsigned long long tmp = 0;
    					for(int l = 0, o = 1; l <= j; ++l, o = 1ll * o * now % mod) {
    						tmp += 1ull * dp[i][op][j - l] * C[j][l] % mod * o;
    						if(!(l & 15)) tmp %= mod;
    					}
    					tmp %= mod;
    					fmod(ans += 1ll * tmp * a[j] % mod);
    				}
    			}
    			fmod(now += pw2[i]);
    		}
    	}
    	int sum = 0;
    	for(int i = k; i < n; ++i)
    		if(S[i] == '1') fmod(sum += pw2[i]);
    	fmod(ans += 1ll * ((mod + 1) >> 1) * lagrange(sum, k, a) % mod);
    	cout << ans << '
    ';
    }
    

    T2 积木小赛

    签到。

    (s) 建子序列自动机,对 (t) 字符串哈希。

    暴力枚举 (t)(O(n^2)) 个子串。

    ([i,j]) 这段区间可以通过子序列自动机找到最优的匹配位置转移到 ([i,j+1])。(显然越靠左越优吧),如果失配就退出。

    复杂度 (O(n^2log n))

    考场上脑抽了拿 map 去重,然后被卡常了/tuu

    改单哈希,模数开成 1e16 级别,存下所有值然后 sort unique。

    然后还是被卡常了/tuu

    md 我松式基排你再卡?

    然后过去了,(1s+ o 0.3s)

    以前从来没感觉 sort 跑不动 1e7 啊,怎么回事?

    哦,没开O2啊/tuu

    Code
    #include<bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define mkp(x,y) make_pair(x,y)
    #define pb(x) push_back(x)
    #define sz(v) (int)v.size()
    typedef long long LL;
    typedef double db;
    template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
    template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
    #define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
    #define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    	return f?x:-x;
    }
    typedef unsigned long long ull;
    const int N=3005;
    int n,len,nxt[N][26],a[N],b[N],ans;
    ull lsh[N*N/2];
    char S[N],T[N];
    const ull mod=10000000000000061ll;
    const int base=131;
    void qsort(ull*a,int n){
    	static int cnt[256];
    	static ull _b[N*N/2],*b=_b;
    	for(int i=0;i<64;i+=8){
    		memset(cnt, 0, sizeof(cnt));
    		for(int j=n-1;j>=0;--j)++cnt[(a[j]>>i)&255];
    		for(int j=1;j<256;++j)cnt[j]+=cnt[j-1];
    		for(int j=n-1;j>=0;--j)b[--cnt[(a[j]>>i)&255]]=a[j];
    		swap(a,b);
    	}
    }
    signed main(){
    	scanf("%d%s%s",&n,S+1,T+1);
    	rep(i,1,n)a[i]=S[i]-'a',b[i]=T[i]-'a';
    	rep(i,0,25)nxt[n][i]=n+1;
    	per(i,n,1){
    		rep(j,0,25)nxt[i-1][j]=nxt[i][j];
    		nxt[i-1][a[i]]=i;
    	}
    	rep(i,1,n){
    		int now=0;
    		ull h=0;
    		rep(j,i,n){
    			now=nxt[now][b[j]];
    			if(now>n)break;
    			h=(h*base+b[j]+1)%mod;
    			lsh[++len]=h;
    		}
    	}
    	qsort(lsh+1,len);
    	rep(i,1,len)if(lsh[i]!=lsh[i-1])++ans;
    	cout<<ans<<'
    ';
    	return 0;
    }
    

    T3 岛屿探险

    果然我的 DS 水平过逊了。

    部分分启发性挺不错的,为良心出题人点赞!但是std为啥是cdq分治啊

    考虑线段树分治,把每一个询问 ([l,r]) 拆成 (O(log n)) 段区间挂到线段树节点上,这样只用考虑一个集合的情况了。

    然后也非常显然,把这个区间内的元素按照 (b) 升序排,这个节点的询问按照 (d) 降序排。

    这么做是为了尝试去掉 (min),否则根本没法做。

    可以发现我们需要维护两个集合:

    • 第一个支持:
      • 加入一个数对 ((a,b))
      • 给定一个数 (c),查询有多少个数对满足 (aigoplus cle b)
      • 删除一个数对。
    • 第二个支持:
      • 加入一个数 (a)
      • 给定两个数 (c,d),查询有多少个数满足 (aigoplus cle d)

    首先把区间内所有元素插到第一个集合,随着 (d) 减小会不断把第一个集合内的元素移到第二个集合。

    可以发现这两个集合都可以用 (Trie) 树很方便的维护。

    这个具体怎么维护还是建议自己想,当然代码里自己写的时候也加了点注释,可以参考。

    复杂度 (O(nlog nlog w))

    然而我的常数还是非常大,以至于我都不能用 vector 存每个节点的询问,还得加上每个节点没有询问的减枝才能过去/tuu。

    Code
    #include<bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define mkp(x,y) make_pair(x,y)
    #define pb(x) push_back(x)
    #define sz(v) (int)v.size()
    typedef long long LL;
    typedef double db;
    template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
    template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
    #define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
    #define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    	return f?x:-x;
    }
    
    const int N = 100005;
    const int M = N << 2;
    int n, m, ans[N];
    struct node {
    	int c, d, id;
    	node() { c = d = id = 0; }
    	node(int c_, int d_, int id_) { c = c_, d = d_, id = id_; }
    } b[M], o[M];
    int hed[N * 20], nxt[N * 20], et, to[N * 20];
    inline void push(int p, int x) {
    	to[++et] = x, nxt[et] = hed[p], hed[p] = et;
    }
    inline bool cmp1(const node &a, const node &b) {
    	return a.d > b.d;
    }
    struct plc {
    	int a, b;
    	plc() { a = b = 0; }
    	plc(int a_, int b_) { a = a_, b = b_; }
    } a[N], w[N];
    inline bool cmp2(const plc &a, const plc &b) {
    	return a.b < b.b;
    }
    
    namespace tr1 {
    /*
    a_i xor c <= b_i
    修改:在 a_i xor k <= b_i 的子树根打标记
    查询:走一遍 c,统计走到的标记和
    */
    const int T = N * 25 * 2;
    int tot, tr[T][2], tag[T];
    inline int New() {
    	++tot;
    	tr[tot][0] = tr[tot][1] = tag[tot] = 0;
    	return tot;
    }
    inline void clear() { tot = 0, New(); }
    inline void insert(const plc &x, int d) {
    	int u = 1, v1 = x.a, v2 = x.b;
    	for(int i = 23; i >= 0; --i) {
    		int c = v1 >> i & 1;
    		if(v2 >> i & 1) {
    			if(!tr[u][c]) tr[u][c] = New();
    			tag[tr[u][c]] += d;
    			if(!tr[u][!c]) tr[u][!c] = New();
    			u = tr[u][!c];
    		} else {
    			if(!tr[u][c]) tr[u][c] = New();
    			u = tr[u][c];
    		}
    	}
    	tag[u]+=d;
    }
    inline int query(const node &x) {
    	int u = 1, res = 0, v = x.c;
    	for(int i = 23; i >= 0; --i) {
    		int c = v >> i & 1;
    		u = tr[u][c], res += tag[u];
    	}
    	return res;
    }
    
    }
    
    namespace tr2 {
    /*
    a_i xor c <= d
    修改:统计每一个子树内 a 的个数 
    查询:c 从上到下在 trie 上走,顺便统计答案,注意叶子结点贡献 
    */
    const int T = N * 25;
    int tot, sum[T], tr[T][2];
    inline int New() {
    	++tot;
    	sum[tot] = tr[tot][0] = tr[tot][1] = 0;
    	return tot;
    }
    inline void clear() { tot = 0, New();}
    inline void insert(plc x, int d) {
    	int u = 1, v = x.a;
    	for(int i = 23; i >= 0; --i) {
    		int c = v >> i & 1;
    		if(!tr[u][c]) tr[u][c] = New();
    		u = tr[u][c], sum[u] += d;
    	}
    }
    inline int query(const node &x) {
    	int u = 1, res = 0, v1 = x.c, v2 = x.d;
    	for(int i = 23; i >= 0; --i) {
    		int c = v1 >> i & 1;
    		if(v2 >> i & 1) res += sum[tr[u][c]], u = tr[u][!c];
    		else u = tr[u][c];
    	}
    	res += sum[u];
    	return res;
    }
    
    }
    
    #define lc (p << 1)
    #define rc (p << 1 | 1)
    void update(int ql, int qr, int d, int l, int r, int p) {
    	if(ql <= l && r <= qr) return push(p, d), void();
    	int mid = (l + r) >> 1;
    	if(ql <= mid) update(ql, qr, d, l, mid, lc);
    	if(mid < qr) update(ql, qr, d, mid + 1, r, rc);
    }
    /*
    按照 b 递增 d 递减排
    左半边 tr1 处理,右半边 tr2 处理
    每处理询问前把 b > d 的移动到 tr2
    */
    void solve(int l, int r, int p) {
    	int t1 = 0, t2 = 0;
    	rep(i, l, r) w[++t1] = a[i];
    	for(int i = hed[p]; i; i = nxt[i])
    		b[++t2] = o[to[i]];
    	if(t2) {
    		sort(b + 1, b + t2 + 1, cmp1);
    		sort(w + 1, w + t1 + 1, cmp2);
    		tr1::clear(),tr2::clear();
    		rep(i, 1, t1) tr1::insert(w[i], 1);
    		int it = t1;
    		for(int i = 1; i <= t2; ++i) {
    			while(it >= 1 && w[it].b > b[i].d)
    				tr1::insert(w[it], -1), tr2::insert(w[it], 1), --it;
    			ans[b[i].id] += tr1::query(b[i]) + tr2::query(b[i]);
    		}
    	}
    	if(l == r) return;
    	int mid = (l + r) >> 1;
    	solve(l, mid, lc), solve(mid + 1, r, rc);
    }
    signed main() {
    	n = read(), m = read();
    	rep(i, 1, n) a[i].a = read(), a[i].b = read();
    	rep(i, 1, m) {
    		int l = read(), r = read(), c = read(), d = read();
    		o[i] = node(c, d, i);
    		update(l, r, i, 1, n, 1);
    	}
    	solve(1, n, 1);
    	rep(i, 1, m) printf("%d
    ", ans[i]);
    	return 0;
    }
    

    总结

    出题人太松了!!!

    不开 O2 太恶心了!!!

    题目质量不错,卡常毒瘤!!!

    还有非常 /tuu 的是码量有点大啊,加起来码了 8.3k !!!

    考场上自己傻逼没办法啊,T1 这么傻逼的错误调了一整场,,,/tuu

    建议补题的时候不要开 O2,体验一下卡常的快感(逃

    路漫漫其修远兮,吾将上下而求索
  • 相关阅读:
    PostgreSQL缺省值
    PostgreSQL表的基本概念
    PostgreSQL调用函数
    4.2. PostgreSQL值表达式
    3.5. PostgreSQL继承
    3.4. PostgreSQL事务
    3.3. PostgreSQL外键
    3.2. PostgreSQL视图
    碰撞
    骨骼
  • 原文地址:https://www.cnblogs.com/zzctommy/p/14599800.html
Copyright © 2011-2022 走看看