ICPC2023 杭州站 (20240909训练)
目录
比赛链接
总结
知识点
B - Festival Decorating
-
可以作为多项式卷积里面多项式的系数 -
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
-
利用容错性,只需要处理前
个元素里能否得到每一个 -
不考虑颜色就是简单的 FFT。考虑类似于 FFT 做字符串匹配的时候的思路,构造匹配函数:令
表示 位置的灯的颜色,没有灯的时候为 0,令 ,只需要计算 -
做法2
- bitset,直接从小到大枚举灯的编号
i
,设其它的灯出现的位置集合是f
,还没有找到答案的d
的集合为n_vis
,我们拿出n_vis & (f >> x[i])
中的所有 1 更新答案即可 - 如何得到
f
:- 先记录所有灯的位置集合
all
- 若颜色为
c[i]
的灯数量不超过 ,直接枚举所有同色灯,然后修改all
- 若颜色为
c[i]
的灯数量超过 ,这样的颜色不超过 个,预处理该颜色所有灯的位置g[c[i]]
然后all ^= g[c[i]]
- 先记录所有灯的位置集合
- 取
可以得到 的复杂度
//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
题意:求下列方程的一组解
解:考虑令
令
//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
设区间
用可持久化线段树,由 rt[nxt[l] + 1]
和 rt[l+1]
的一部分,合并得到 rt[l]
。具体来说,先找到 [l, nxt[l]], [nxt[l]+1, n]
这两个区间被线段树划分出来的
//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
首先,如果
考虑
- 符号约定:
- 定义
表示 的第 个到第 个字符构成的字符串 - 定义
当且仅当每个字母在两个串中的出现次数相同
- 定义
,只需要保证 是 的前缀,即保证 ,设 ,则需要保证:- ……
- 故而,我们只要找出满足若干“某前缀
某个字符串”要求的 即可。
这个做法可以扩展到
,则直接将 中对前缀的限制条件转移到 相应位置上 ,考虑对 长度为 的前缀的限制,该限制等价于对 的长度为 的前缀有限制,并且被限制 的字符串等于“原限制中的字符串扣掉 个 ”
//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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效