ICPC2023 杭州站 (20240909训练)

比赛链接

link to contest

总结

知识点

B - Festival Decorating

  • [Ai0] 可以作为多项式卷积里面多项式的系数

  • bitset 可以做01卷积;此时,每一侧都可以作为最外侧所枚举的:

    •   for(int i=1; i<=n; ++i) f.set(x[i]);
        //for(int i=0; i<=m; ++i)
        //	ans[i] = (f & (f>>i)).count();
        //等价写法:
        for(int i=1; i<=n; ++i)
            ans |= f>>x[i];
      
  • bitset 的 _Find_first_Find_next

    •   for(int t=f._Find_first(); t!=f.size(); t=f._Find_next(t))
      

D - Operator Precedence

  • 构造题的方法:“待定系数”、列方程求解

K - Card Game

  • “对所有区间询问”,可以考虑用可持久化线段树预处理“所有区间”的答案

易错点

  • 注意讨论“元素相等”的边界情况(H - Sugar Sweet II)

策略

  • 多想一下再开写(M刚开的时候有好几个思路上的大漏洞)
  • 过题人数与难度不一定正比(G,F 开晚了;K可以开)

题解

B - Festival Decorating

做法1

  • 利用容错性,只需要处理前 3x 个元素里能否得到每一个 d

  • 不考虑颜色就是简单的 FFT。考虑类似于 FFT 做字符串匹配的时候的思路,构造匹配函数:令 Ai 表示 i 位置的灯的颜色,没有灯的时候为 0,令 Bi=Ai,只需要计算 Cd=i[Ai>0][Bi+d>0](AiBi+d)2

    • Cd=i[Ai>0][Bi+d>0](Ai2+Bi+d22AiBi+d)=iAi2[Bi+d>0]+i[Ai>0]Bi+d22iAiBi+d

做法2

  • bitset,直接从小到大枚举灯的编号 i,设其它的灯出现的位置集合是 f,还没有找到答案的 d 的集合为n_vis,我们拿出 n_vis & (f >> x[i]) 中的所有 1 更新答案即可
  • 如何得到 f
    • 先记录所有灯的位置集合 all
    • 若颜色为 c[i] 的灯数量不超过 T,直接枚举所有同色灯,然后修改all
    • 若颜色为 c[i] 的灯数量超过 T,这样的颜色不超过 nT 个,预处理该颜色所有灯的位置 g[c[i]] 然后 all ^= g[c[i]]
  • T=n 可以得到 O(nn+n2w)​ 的复杂度
//https://codeforces.com/group/Ro45Qpwf2L/contest/104976/submission/280692536
const int N = 250010;
int x[N], c[N], n, m;
vector<int> s[N];

bitset<250005> sum, f[1010], nvis, tmp;
int ans[N];
int pos[N], id[N], num;

int main() {
	int q; rd(n), rd(q);
	for(int i=1; i<=n; ++i) {
		rd(x[i]), rd(c[i]);
		m = max(m, x[i]), s[c[i]].PB(x[i]);
	}

	int B = ceil(sqrt(n));
	for(int i=1; i<=n; ++i) if(s[i].size()) {
		for(int v : s[i]) sum.set(v);
		if(s[i].size() > B) {
			id[pos[i] = ++num] = i;
			for(int v : s[i])
				f[num].set(v);
		}
	}

	for(int i=1; i<=m; ++i) nvis.set(i);

	for(int i=1; i<=n; ++i) {
		if(s[c[i]].size() > B)
			tmp = (sum^f[pos[c[i]]]);
		else {
			tmp = sum;
			for(int v : s[c[i]]) tmp.reset(v);
		}
		tmp = (tmp >> x[i]) & nvis;
		for (int t=tmp._Find_first();t!=tmp.size();t=tmp._Find_next(t))
			ans[t] = i, nvis.reset(t);
	}

	while(q--) {
		int d; rd(d);
		printf("%d\n", (int)(ans[d]));
	}
	return 0;
}

D - Operator Precedence

题意:求下列方程的一组解

(a1×a2)+(a3×a4)++(a2n1×a2n)=a1×(a2+a3)×(a4+a5)××(a2n2+a2n1)×a2n

解:考虑令 a2,a4a2n2=2,a3,a5,,a2n1=1,设 a1=x,a2n=y,则

xy=2xy2(n2)

x=1y=3n​,得到一组合法解。

//https://codeforces.com/group/Ro45Qpwf2L/contest/104976/submission/280942688
int main() {
	int ts; rd(ts);
	while(ts--) {
		int n; rd(n);
		if(n == 3) {
			printf("1 -10 6 6 -10 1\n");
			continue;
		}
		print(1), putchar(' ');
		for(int i=2; i<2*n; ++i)
			print(i&1 ? -1 : 2), putchar(' ');
		print(3-n), putchar('\n');
	}
	return 0;
}

K - Card Game

设区间 [l,r] 的答案为 ansl,r,设 nxti 表示 ai 在序列中下一次出现的位置。则:

ansl,r={ansnxtl+1,rnxtlr1+ansl+1,rnxtl>r

用可持久化线段树,由 rt[nxt[l] + 1]rt[l+1] 的一部分,合并得到 rt[l]。具体来说,先找到 [l, nxt[l]], [nxt[l]+1, n] 这两个区间被线段树划分出来的 O(logn) 个区间,区间及其祖先新建节点,区间的儿子继承之前的树。

//https://codeforces.com/group/Ro45Qpwf2L/contest/104976/submission/280699787
const int N = 3e5 + 10;
const int M = 60 * N;
int ls[M], rs[M], tg[M];
int ncnt;

int ql, qr, qt;

void upd(int l,int r,int &c, int u, int t) {
	if(!c) c = ++ncnt;
	t += tg[u];
	if(ql <= l && qr >= r) {
		tg[c] += qt + t;
		ls[c] = ls[u], rs[c] = rs[u];
		return;
	}
	int mid = l + r >> 1;
	if(ql <= mid) upd(l, mid, ls[c], ls[u], t);
	if(qr > mid) upd(mid+1, r, rs[c], rs[u], t);
}
 
int qry(int l,int r,int c) {
	if(!c || l == r) return tg[c];
	int mid = l + r >> 1;
	if(ql <= mid) return qry(l, mid, ls[c]) + tg[c];
	else return qry(mid+1, r, rs[c]) + tg[c];
}
 
int rt[N];
 
int n, a[N], p[N], lst[N];
 
int main() {
	int q; rd(n), rd(q);
	for(int i=1; i<=n; ++i) rd(a[i]);
	
	for(int i=1; i<=n; ++i) lst[i] = n+1;
	for(int i=n; i>=1; --i) {
		p[i] = lst[a[i]];
		lst[a[i]] = i;
	}
 
	for(int i=n; i>=1; --i) {
		ql = i, qr = p[i] - 1, qt = 1;
		upd(1, n, rt[i], rt[i + 1], 0);
		ql = p[i], qr = n, qt = 0;
		if(ql <= qr) upd(1, n, rt[i], rt[p[i] + 1], 0);
	}
	int ans = 0;
	while(q--) {
		int l, r; rd(l), rd(r);
		l ^= ans, r ^= ans;
		// printf("query: %d %d\n", l, r);
		ql = r;
		printf("%d\n", ans = qry(1, n, rt[l]));
	}
	return 0;
}

E - Period of a String

首先,如果 si 确定,则 si+1,si+2,sn 均确定。

考虑 n=2​ 时的做法:

  • 符号约定:
    • 定义 s[l,r] 表示 s 的第 l 个到第 r 个字符构成的字符串
    • 定义 ss 当且仅当每个字母在两个串中的出现次数相同
  • |s1||s2| ,只需要保证 s2s1 的前缀,即保证 s1[1,|s2|]s2[1,|s2|]
  • |s1|<|s2|,设 |s2|=T|s1|+R(1R|s1|) ,则需要保证:
    • s1[1,|s1|]s2[1,|s1|]
    • s1[1,|s1|]s2[|s1|+1,2|s1|]
    • ……
    • s1[1,R]s2[T|s1|+1,|s2|]
  • 故而,我们只要找出满足若干“某前缀某个字符串”要求的 s1 即可。

这个做法可以扩展到 n>2 的情形上:

  • |si1||si|,则直接将 |si| 中对前缀的限制条件转移到 si1 相应位置上
  • |si1|<|si|,考虑对 si 长度为 L(>|si1|) 的前缀的限制,该限制等价于对 si1 的长度为 ((L1)mod|si1|)+1 的前缀有限制,并且被限制 的字符串等于“原限制中的字符串扣掉 L1|si1|si1

i=n,n1,2 扫一遍,依据最终的限制确定 s1,再根据 s1 依次确定 s2,sn。复杂度 O(|Σ||si|)

//https://codeforces.com/gym/104976/problem/E
bool solve() {
	int mxlen = 0;
	for(int i=1; i<=n; ++i) mxlen = max(mxlen, len[i]);
 
	for(int j=0; j<=mxlen; ++j) {
		vis[j] = 0;
		for(int k=0; k<M; ++k)
			c[j][k] = 0;
	}
 
	int curlen = 0;
	for(int i=n; i>=1; --i) {
		if(curlen > len[i]) {
			int T = (curlen - 1) / len[i];
			for(int j = len[i]+1; j<=curlen; ++j) if(vis[j]) {
				int d[26] = {0};
				for(int k=0; k<26; ++k) d[k] = c[j][k] - (j-1)/len[i]*b[i][k];
				if(!add((j-1) % len[i] + 1, d)) return 0;
				vis[j] = 0;
			}
		}
		curlen = len[i];
		if(!add(len[i], b[i])) return 0;
		int last[26] = {0};
		for(int j=1; j<=len[i]; ++j) if(vis[j]) {
			for(int k=0; k<26; ++k) {
				if(c[j][k] < last[k]) return 0;
				last[k] = c[j][k];
			}
		}
	}
 
	int last[26] = {0};
	for(int j=1; j<=curlen; ++j) if(vis[j]) {
		for(int k=0; k<26; ++k) {
			int diff = c[j][k] - last[k];
			last[k] = c[j][k];
			c[j][k] = diff;
		}
	}
	vector<int> Q;
	for(int j=curlen; j>=1; --j) {
		if(vis[j]) {
			for(int k=0; k<26; ++k)
				for(int x=0; x<c[j][k]; ++x)
					Q.PB(k);
		}
		S[j] = Q.back() + 'a'; Q.pop_back();
	}
 
	printf("YES\n");
	for(int i=1; i<=n; ++i) {
		for(int j=1; j<=len[i]; ++j) {
			S[j] = S[(j-1) % curlen + 1];
			putchar(S[j]);
		}
		putchar('\n');
		curlen = len[i];
	}
 
	return 1;
}
int main() {
	int ts; rd(ts);
	while(ts--) {
		rd(n);
		for(int i=1; i<=n; ++i) {
			memset(b[i], 0, sizeof(b[i]));
			scanf("%s", tmp + 1);
			len[i] = strlen(tmp + 1);
			for(int j=1; j<=len[i]; ++j)
				b[i][tmp[j]-'a'] ++;
		}
		if(!solve()) {
			printf("NO\n");
			continue;
		}
	}
	return 0;
}
posted @   zhongyuwei  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示