Codeforces 杂题 round 1
目录:
Edu103(div.2) E
cf round700(div.2) C
cf round683(div.2) D
cf round683(div.2) E
cf round666(div.2) D
cf round707(div.2) C
Edu82(div.2) D
cf round391(div.1+2) G
Edu103(div.2) E
考虑怎么匹配字符串,关键在于通配符
可以根据 n 个含有通配符的串建立 trie 树,然后枚举 mt 串通配符的位置,显然每个串有 2k 种情况,那么就进行 2^k\times m 次匹配
题目要求位于 k 的串第一个被匹配,那么正难则反地去做,可以保证其他串都在他之前出现,这个可以连边转化为闭合子图,然后 topo 序再反过来就是答案
1 #include <ctime> 2 #include <cmath> 3 #include <cctype> 4 #include <cstdio> 5 #include <cstring> 6 #include <cstdlib> 7 #include <iostream> 8 #include <algorithm> 9 #include <vector> 10 #include <queue> 11 #include <bitset> 12 #define inf 100010 13 #define INF 0x7fffffff 14 #define ll long long 15 16 template <class I> 17 inline void read(I &num){ 18 num = 0; char c = getchar(), up = c; 19 while(!isdigit(c)) up = c, c = getchar(); 20 while(isdigit(c)) num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar(); 21 up == '-' ? num = -num : 0; return; 22 } 23 template <class I> 24 inline void read(I &a, I &b) {read(a); read(b);} 25 template <class I> 26 inline void read(I &a, I &b, I &c) {read(a); read(b); read(c);} 27 28 struct edge { 29 int to; 30 edge *nxt; 31 }; 32 33 edge *g[inf]; 34 int n, m, k; 35 char str[inf][10]; 36 int app[inf]; 37 38 inline void connect(int from, int to){ 39 static edge pool[inf * 30]; 40 static edge* p = pool; 41 p->to = to; 42 p->nxt = g[from]; 43 g[from] = p; p++; 44 return; 45 } 46 47 class Trie { 48 private: 49 struct node { 50 int son[28]; 51 int id; 52 node() { 53 memset(son, -1, sizeof (son)); 54 id = -1; 55 } 56 }; 57 58 std::vector <node> t; 59 60 inline int transInt(char c) {return (c == '_' ? 26 : c - 'a');} 61 62 public: 63 inline void insert(int x, int now, int id, char *s) { 64 if(now == k + 1) { 65 t[x].id = id;; 66 return; 67 } 68 int nxt = transInt(s[now]); 69 if(t[x].son[nxt] == -1) { 70 t[x].son[nxt] = t.size(); 71 t.push_back(node()); 72 } 73 insert(t[x].son[nxt], now + 1, id, s); 74 } 75 76 inline void query(int x, int now, int id, char *s) { 77 if(now == k + 1) { 78 if(t[x].id == id) app[id] = 1; 79 else connect(id, t[x].id); 80 return; 81 } 82 int nxt = transInt(s[now]); 83 if(t[x].son[nxt] != -1) 84 query(t[x].son[nxt], now + 1, id, s); 85 if(s[now] != '_' && t[x].son[transInt('_')] != -1) 86 query(t[x].son[transInt('_')], now + 1, id, s); 87 return; 88 } 89 90 Trie () { 91 t.clear(); 92 t.push_back(node()); 93 } 94 }; 95 96 Trie t; 97 char s[inf][10]; 98 int mt[inf]; 99 std::vector <int> ord; 100 int vis[inf]; 101 102 inline void fail() { 103 puts("NO"); 104 exit(0); 105 } 106 107 void dfs(int x) { 108 vis[x] = 1; 109 for(auto e = g[x]; e; e = e->nxt) { 110 auto y = e->to; 111 if(vis[y] == 1) fail(); 112 if(vis[y] == 0) dfs(y); 113 } 114 vis[x] = 2; 115 ord.push_back(x); 116 } 117 118 inline void setting(){ 119 #ifndef ONLINE_JUDGE 120 freopen("E.in", "r", stdin); 121 freopen("E.out", "w", stdout); 122 #endif 123 return; 124 } 125 126 signed main(){ 127 setting(); 128 read(n, m, k); 129 for(int i = 1; i <= n; i++) scanf("%s", str[i] + 1); 130 for(int i = 1; i <= n; i++) t.insert(0, 1, i, str[i]); 131 for(int i = 1; i <= m; i++) scanf("%s", s[i] + 1), read(mt[i]); 132 for(int i = 1; i <= m; i++) { 133 t.query(0, 1, mt[i], s[i]); 134 if(app[mt[i]] == 0) fail(); 135 } 136 for(int i = 1; i <= n; i++) { 137 if(vis[i] == 0) dfs(i); 138 } 139 puts("YES"); 140 std::reverse(ord.begin(), ord.end()); 141 for(auto i: ord) printf("%d ", i); puts(""); 142 return 0; 143 }
cf round700(div.2) C
容易想到二分,但是怎么二分呢?
考虑排列是 1,2,3,...,n,此时答案就是 1,最序列的最左侧,当排列非这样,则答案位置可能右移,但这个序列一定有答案
同样的,当序列是 n,..,3,2,1 时,可以与上面同理,答案在最右侧
那么二分时,查 mid,mid-1,mid+1,当 mid-1 的值小于 mid 位置时,可以递归到左侧,如果 mid+1 位置小于 mid 位置,可以递归到右侧
如此递归即可
1 #include <ctime> 2 #include <cmath> 3 #include <cctype> 4 #include <cstdio> 5 #include <cstring> 6 #include <cstdlib> 7 #include <iostream> 8 #include <algorithm> 9 #include <vector> 10 #include <queue> 11 #define inf 100010 12 #define INF 0x7fffffff 13 #define ll long long 14 15 template <class I> 16 inline void read(I &num){ 17 num = 0; char c = getchar(), up = c; 18 while(!isdigit(c)) up = c, c = getchar(); 19 while(isdigit(c)) num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar(); 20 up == '-' ? num = -num : 0; return; 21 } 22 template <class I> 23 inline void read(I &a, I &b) {read(a); read(b);} 24 template <class I> 25 inline void read(I &a, I &b, I &c) {read(a); read(b); read(c);} 26 27 int n; 28 int a[inf]; 29 30 inline bool check(int p) { 31 int a, b, c; 32 if(p - 1 < 1) a = INF; 33 else { 34 std::cout << "? " << p - 1 << std::endl; 35 std::cin >> a; 36 } 37 std::cout << "? " << p << std::endl; 38 std::cin >> b; 39 if(p + 1 > n) c = INF; 40 else { 41 std::cout << "? " << p + 1 << std::endl; 42 std::cin >> c; 43 } 44 if(a > b && b < c) { 45 std::cout << "! " << p << std::endl; 46 exit(0); 47 } 48 if(a < b) return 0; 49 return 1; 50 } 51 52 signed main(){ 53 std::cin >> n; 54 int l = 1, r = n + 1; 55 while(l < r - 1) { 56 int mid = (l + r) >> 1; 57 if(check(mid)) l = mid; 58 else r = mid; 59 } 60 std::cout << "! " << l << std::endl; 61 std::cout.flush(); 62 return 0; 63 }
CF round683(div.2) D
考虑 dp,设 dp_{i,j} 表示第一个串 i 位置的某个后缀,第二个串 j 位置的某个后缀的最优答案,那么考虑转移,如果当前位置字符是一样的,那么就是在原来基础上各加了一个位置,那么就是答案就要 +4-2 = +2,如果当前位置不同,那么考虑把两个串各往后移一位,那么就是不增加 LCS,但是长度 +1,那么答案就是 -1
1 #include <ctime> 2 #include <cmath> 3 #include <cctype> 4 #include <cstdio> 5 #include <cstring> 6 #include <cstdlib> 7 #include <iostream> 8 #include <algorithm> 9 #include <vector> 10 #include <queue> 11 #define inf 5010 12 #define INF 0x7fffffff 13 #define ll long long 14 15 template <class I> 16 inline void read(I &num){ 17 num = 0; char c = getchar(), up = c; 18 while(!isdigit(c)) up = c, c = getchar(); 19 while(isdigit(c)) num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar(); 20 up == '-' ? num = -num : 0; return; 21 } 22 template <class I> 23 inline void read(I &a, I &b) {read(a); read(b);} 24 template <class I> 25 inline void read(I &a, I &b, I &c) {read(a); read(b); read(c);} 26 27 int f[inf][inf]; 28 int n, m; 29 char s[inf], t[inf]; 30 31 signed main(){ 32 read(n, m); 33 std::cin >> (s + 1) >> (t + 1); 34 int ans = -INF; 35 for(int i = 1; i <= n; i++) { 36 for(int j = 1; j <= m; j++) { 37 if(s[i] == t[j]) f[i][j] = f[i - 1][j - 1] + 2; 38 else f[i][j] = std::max (f[i - 1][j], f[i][j - 1]) - 1; 39 f[i][j] = std::max (f[i][j], 0); 40 ans = std::max (ans, f[i][j]); 41 } 42 } 43 std::cout << ans << '\n'; 44 return 0; 45 }
cf round683(div.2) E
考虑一个好的序列无非是满足 n-1 条连边且全部联通,n-1 条连边无非是指存在且只存在一对数互相连边
我们可以发现一个性质,如果把 n 个数按照最高位是 0 还是 1 分成两个集合的话,那么显然两个集合间不会连边,除非有一个集合大小是 1
所以,答案只能是两个集合的最大答案 +1,这个 +1 就是加上了隔壁集合的一个元素
所以我们需要去找两个分集合的最大答案,递归即可
n 个元素,所以最短递归 30 \times n 次
#include <ctime> #include <cmath> #include <cctype> #include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <vector> #include <queue> #define inf 200010 #define INF 0x7fffffff #define ll long long template <class I> inline void read(I &num){ num = 0; char c = getchar(), up = c; while(!isdigit(c)) up = c, c = getchar(); while(isdigit(c)) num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar(); up == '-' ? num = -num : 0; return; } template <class I> inline void read(I &a, I &b) {read(a); read(b);} template <class I> inline void read(I &a, I &b, I &c) {read(a); read(b); read(c);} int n; int nxt[inf * 30][2]; inline void getnxt(int x) { static int index = 0; int now = 0; for(int i = 30; i >= 0; i--) { bool c = x & (1 << i); if(nxt[now][c]) now = nxt[now][c]; else now = nxt[now][c] = ++index; } } int dfs(int x) { if((nxt[x][0] || nxt[x][1]) == 0) return 1; if(nxt[x][0] == 0) return dfs(nxt[x][1]); else if(nxt[x][1] == 0) return dfs(nxt[x][0]); else return std::max (dfs(nxt[x][0]), dfs(nxt[x][1])) + 1; } signed main(){ read(n); for(int i = 1; i <= n; i++) { int x; read(x); getnxt(x); } std::cout << (n - dfs(1)) << '\n'; return 0; }
cf round666(div.2) D
luogu 你给我讲这是个黄题?
考虑最大一堆大于其他所有堆之和的情况,容易发现先手必胜
那么对于不满足这个条件的游戏状态,先手一定不愿意把当前状态转化成上述状态
所以轮流拿石子的结果就是这所有石子一定会被取到只有一堆,而这一堆一定只有一个
证明:设最后一堆剩了 {x}(x > 1) 个,则上一个状态一定是 {1,x} 个,为了防止下一个状态出现先手必胜态,当前玩家一定会选择拿走一个 x 堆的,递归成 {1,x-1},证毕
qc:打表容易得到答案
cf round707(div.2) C
震撼我妈的题,可以发现如果有多于或等于 2 个数出现过两次及以上,那么答案显然。对于其他情况,可以考虑相邻两两做差,a-c=d-b 就转化为找两段区间和相同的子区间,然后就死了,想各种分治做法,乱搞做法都暴毙了
然后发现就是个鸽笼原理傻逼题,数据范围 a_i\leq 2.5\times 10^6,那么两两之和最大为 5e6,所以直接暴力 O(n^2) 即可,最多枚举 5e6 次一定出答案
abcd 可能相同,可以直接特判掉,复杂度变成 O(n^2+C),但是感觉或许能被卡死,于是各种特判,结果就是调了一小时才 A 掉
Edu82(div.2) D
二进制拆分,对于每一个 1,如果有一个正好对应他这个位置的物品,就直接用掉
对于没有对应物品的位置,我们首先希望有若干个比他小的物品凑出这个位置,那就直接向前遍历,否则我们需要拆大的物品,需要拆的次数就是二进制下位数差,可以证明拆离他最近的物品肯定最优
#include <ctime> #include <cmath> #include <cctype> #include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <vector> #include <queue> #define inf 100007 #define N 100 #define P 63 #define INF 0x7fffffff #define int long long template <class I> inline void read(I &num){ num = 0; char c = getchar(), up = c; while(!isdigit(c)) up = c, c = getchar(); while(isdigit(c)) num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar(); up == '-' ? num = -num : 0; return; } template <class I> inline void read(I &a, I &b) {read(a); read(b);} template <class I> inline void read(I &a, I &b, I &c) {read(a); read(b); read(c);} int n, m; int a[inf]; int cnt[N]; int tmp[N]; inline void solve() { read(n, m); int ans = 0; memset(cnt, 0, sizeof(cnt)); for(int i = 1; i <= m; i++) { read(a[i]); for(int j = 0; j <= P; j++) { if((1ll << j) & a[i]) { ++cnt[j]; break; } } } for(int i = 0; i <= P; i++) { if((1ll << i) & n) { if(cnt[i]) --cnt[i]; else { int goal = (1ll << i); memset(tmp, 0, sizeof (tmp)); for(int j = i - 1; j >= 0; j--) { while(cnt[j] && (1ll << j) <= goal) goal -= (1ll << j), --cnt[j], ++tmp[j]; if(goal == 0) goto success; } for(int j = i - 1; j >= 0; j--) cnt[j] += tmp[j], tmp[j] = 0; for(int j = i + 1; j <= P; j++) { if(cnt[j]) { --cnt[j]; ans += (j - i); for(int k = i; k < j; k++) ++cnt[k]; goto success; } } puts("-1"); return; success: continue; } } } std::cout << ans << '\n'; } signed main(){ int T; read(T); while(T--) solve(); return 0; }
cf round391(div.1+2) G
前置:树链剖分,可持久化线段树,标记永久化
容易发现 dist(u,v)=dep_u+dep_v-2dep_{LCA(u,v)}, 并有 \sum_{i=l}^rdist(i,p_i)=\sum_{i=1}^{r}dist(i,p_i)-\sum_{i=1}^{l-1}dist(i,p_i)
所以需要维护 \sum_{i=1}^ndep_u+\sum_{i=1}^ndep_{p_i}-2\times \sum_{i=1}^ndep_{LCA(u,p_i)}
第一项为常数,第二项前缀和维护,考虑维护第三项
考虑这样一个事,对于 1 到 n 的每一个 p_i,都把他到根的路径上的 cnt+=1,那么从点 u 到根路径上求 \sum_{i\in way} w_i\times cnt_i,就是 \sum_{i=1}^ndep_{LCA(u,p_i)}(设 w_i 为边权)
所以问题转化为路径加,路径求和,直接树链剖分
冷静一下,发现我们实际上需要做的是对一个点 u,求 n 时刻前,u 到 根 的路径和,所以我们树链剖分上实际套的数据结构是主席树
需要实现主席树区间加,标记永久化即可
代码实在有亿点难写,就贴一个标记永久化可持久化线段树做做样子吧(题面)
#include <ctime> #include <cmath> #include <cctype> #include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <cassert> #define inf 100010 #define INF 0x7fffffff #define ll long long template <class I> inline void read(I &num){ num = 0; char c = getchar(), up = c; while(!isdigit(c)) up = c, c = getchar(); while(isdigit(c)) num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar(); up == '-' ? num = -num : 0; return; } template <class I> inline void read(I &a, I &b) {read(a); read(b);} template <class I> inline void read(I &a, I &b, I &c) {read(a); read(b); read(c);} int n, m; int a[inf]; char opt[5]; int root[inf]; ll tag[inf << 4], sum[inf << 4]; int ls[inf << 4], rs[inf << 4]; int Index = -1; void build(int l, int r, int &rt) { rt = ++Index; if(l == r) { sum[rt] = a[l]; return; } int mid = (l + r) >> 1; build(l, mid, ls[rt]); build(mid + 1, r, rs[rt]); sum[rt] = sum[ls[rt]] + sum[rs[rt]]; return; } ll query(int nl, int nr, int l, int r, int rt) { if(l <= nl && r >= nr) return sum[rt]; int mid = (nl + nr) >> 1; ll chg = 1ll * tag[rt] * (std::min (nr, r) - std::max (nl, l) + 1); ll res = 0; if(l <= mid) res += query(nl, mid, l, r, ls[rt]); if(r > mid) res += query(mid + 1, nr, l, r, rs[rt]); return res + 1ll * (l <= mid || r > mid) * chg; } void modify(int nl, int nr, int l, int r, int k, int &rt, int nxt) { rt = ++Index; tag[rt] = tag[nxt]; ls[rt] = ls[nxt], rs[rt] = rs[nxt]; sum[rt] = sum[nxt] + 1ll * k * (std::min (nr, r) - std::max (nl, l) + 1); if(l <= nl && r >= nr) { tag[rt] += k; return; } int mid = (nl + nr) >> 1; if(l <= mid) modify(nl, mid, l, r, k, ls[rt], ls[nxt]); if(r > mid) modify(mid + 1, nr, l, r, k, rs[rt], rs[nxt]); return; } signed main(){ read(n, m); for(int i = 1; i <= n; i++) read(a[i]); int now = 0; build(1, n, root[now]); while(m--) { std::cin >> opt; if(opt[0] == 'Q') { int l, r; read(l, r); printf("%lld\n", query(1, n, l, r, root[now])); } else if(opt[0] == 'C') { int l, r, k; read(l, r, k); ++now; modify(1, n, l, r, k, root[now], root[now - 1]); } else if(opt[0] == 'H') { int l, r, t; read(l, r, t); printf("%lld\n", query(1, n, l, r, root[t])); } else if(opt[0] == 'B') { int t; read(t); now = t, Index = root[t + 1] - 1; } else assert(0); } return 0; }
【推荐】2025 HarmonyOS 鸿蒙创新赛正式启动,百万大奖等你挑战
【推荐】博客园的心动:当一群程序员决定开源共建一个真诚相亲平台
【推荐】开源 Linux 服务器运维管理面板 1Panel V2 版本正式发布
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· C23和C++26的#embed嵌入资源指南
· 「EF Core」框架是如何识别实体类的属性和主键的
· 独立开发,这条路可行吗?
· 我在厂里搞 wine 的日子
· 如何通过向量化技术比较两段文本是否相似?
· 他没买 iPad,而是花了半年时间,为所有“穷学生”写了个笔记神器
· Visual Studio 现已支持新的、更简洁的解决方案文件(slnx)格式
· 只需一行命令,Win11秒变Linux开发主机!
· 上周热点回顾(7.7-7.13)
· 也是出息了,业务代码里面也用上算法了。