暑假集训四[打地鼠,竞赛图,糖果,树]
暑假集训四
倒序更新石锤了。
题面
A.打地鼠
大水题,开考五分钟后全机房的键盘都在响。
枚举左上角,单次统计是 的,考虑二位前缀和优化。
Code
//想到了四分树 //不过这玩意复杂度假的,应该不是正解 #include<cstdio> #include<algorithm> using namespace std; const int MAXN = 2010; int n, k, ans; int num[MAXN][MAXN], sum[MAXN][MAXN]; char s[MAXN]; int Get_Sum(int x1, int y1, int x2, int y2){ return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1]; } int main(){ scanf("%d%d", &n, &k); /* for(register int i = 1; i <= n; i++){ for(register int j = 1; j <= n; j++){ scanf("%1d", &num[i][j]); sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + num[i][j]; } } */ for(register int i = 1; i <= n; i++){ scanf("%s", s + 1); for(register int j = 1; j <= n; j++){ num[i][j] = s[j] ^ 48; sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + num[i][j]; } } //这题怎么看都是能用 n ^ 2 水过去的,难不成就考一个二维前缀和? //和着说二分都不用? //真就二维前缀和水过去? //本来以为和粟粟的书架一样,二维前缀和优化二分答案,然后直接 n ^ 2 枚举过去 for(register int i = 1; i <= n - k + 1; i++){ for(register int j = 1; j <= n - k + 1; j++){ int x1 = i, y1 = j; int x2 = i + k - 1, y2 = j + k - 1; int tot = Get_Sum(x1, y1, x2, y2); ans = max(ans, tot); } } printf("%d", ans); return 0; }
B.竞赛图
看数据范围状压。
发现对一个状态的图来说,将强联通分量缩成一个点后的图中,一定有一个新的点 连接他的所有边都指向其他强联通分量 ,没有其他强联通分量指向 的边。
因为如果有指回 的边,那么一定能构成新的强联通分量,或者出现新的 。
考虑当前状态不合法的图, 与其他集合所有子集的并集都是非法的,因为没有指回 的边,他们一定不能构成强联通分量。
所有合法状态可以看做 ,能够到达的点看做 ,那么 与 的子集的并集都是非法状态,通过枚举 的子集筛去不合法状态。
Code
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN = 25, SIZE = 1 << 25; int t, n, ans; int edge[SIZE]; bool IsSCC[SIZE]; void Clear(){ ans = 0; memset(edge, 0, sizeof(edge)); memset(IsSCC, 0, sizeof(IsSCC)); } int lowbit(int x){ return x & (-x); } inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int main(){ t = read(); while(t--){ Clear(); n = read(); for(register int i = 1; i <= n; i++){ for(register int j = 1; j <= n; j++){ int num; num = read(); edge[1 << (i - 1)] |= num << (j - 1); } } int m = (1 << n) - 1; edge[0] = m; for(register int i = 1; i <= m; i++) edge[i] = edge[lowbit(i)] & edge[lowbit(i) ^ i]; for(register int i = 1; i <= m; i++){ if(!IsSCC[i]){ for(register int j = edge[i]; j; j = (j - 1) & edge[i]) IsSCC[i | j] = true; } } for(register int i = 0; i <= m; i++) if(!IsSCC[i]) ans++; printf("%d\n", ans + 1); } return 0; }
C.糖果
神仙DP,自己也搞不懂,挂Chen_jr大佬的题解了。
Code
#include<cstdio> using namespace std; const int Mod = 1e9 + 7; const int MAXN = 410; int n; long long ans; int a[MAXN], b[MAXN], pos_a[MAXN], pos_b[MAXN]; long long dp[MAXN][MAXN][155][2]; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int main(){ n = read(); for(register int i = 1; i <= n; i++){ a[i] = read(); pos_a[a[i]] = i; } for(register int i = 1; i <= n; i++){ b[i] = read(); pos_b[b[i]] = i; } dp[1][1][0][0] = 1; for(register int i = 1; i <= n + 1; i++){ for(register int j = 1; j <= n + 1; j++){ for(register int k = 0; k <= n / 3; k++){ if(dp[i][j][k][0]){ if(i == n + 1){ if(!k) ans = (ans + dp[i][j][k][0]) % Mod; continue; } if(pos_b[a[i]] < j) dp[i + 1][j][k][0] = (dp[i + 1][j][k][0] + dp[i][j][k][0]) % Mod; else{ dp[i][j][k][1] = (dp[i][j][k][1] + dp[i][j][k][0]) % Mod; if(k) dp[i + 1][j][k - 1][0] = (dp[i + 1][j][k - 1][0] + dp[i][j][k][0] * k % Mod) % Mod; } } if(dp[i][j][k][1]){ if(pos_a[b[j]] < i) dp[i][j + 1][k][1] = (dp[i][j + 1][k][1] + dp[i][j][k][1]) % Mod; else if(a[i] != b[j]){ dp[i + 1][j + 1][k + 1][0] = (dp[i + 1][j + 1][k + 1][0] + dp[i][j][k][1]) % Mod; if(k) dp[i][j + 1][k - 1][1] = (dp[i][j + 1][k - 1][1] + dp[i][j][k][1] * k % Mod) % Mod; } } } } } for(register int i = 1; i <= n; i++) if(i % 3) ans = ans * i % Mod; printf("%lld", ans); return 0; }
D.树
基本原题:P7735 [NOI2021] 轻重边
和NOI2021 D1T1 基本一样,稍有不同。(话说NOIP模拟赛考NOI原题这好吗)
题解写的太麻烦了。
直接维护边难以做到,可以转化成维护点,考虑在修改路径的时候,把修改路径上的每个点全部染上一种独一无二的颜色,可以理解为时间戳,白边两端点颜色(时间戳)相同,黑边两端点颜色(时间戳)不同。
具体些,因为要将一条路径上的边修成白边,那么路径点染同色的操作就可以保证这条路径上点同色且所有边两端点同色。因为染的颜色(时间戳)不会重复,染色点的另一端一定与它的颜色不同,自然而然的,这条路径相邻的边就全变成了黑边。
这种树剖的思想好像毛毛虫剖分来着。
在经过题意转化后,问题变成了:统计一段路径上异色相邻点对的数量(相邻:保证两点间有边;异色:保证是黑边)。
然后就可以用另一道题P2486 [SDOI2011]染色的思想来做了。
对每个线段树节点记录区间左端点颜色,区间右端点颜色,区间相邻异色点对数量。在pushup
的时候,比较两个子节点接头处颜色的情况,更新区间答案。
对于树剖区间的查询,需要让区间左右“有序”,我的方法比较偷懒,直接查询该点的颜色,整体复杂度还是 的,但是常数会大,相较于其他大佬的做法,我整体慢了 多。
Code
//咱std是真能压行啊 #include<cstdio> #include<algorithm> using namespace std; const int MAXN = 3e5 + 10; int n, m, cnt, num, times; int head[MAXN]; int fa[MAXN], son[MAXN], size[MAXN], deep[MAXN]; int dfn[MAXN], top[MAXN]; struct Edge{ int to, next; }e[MAXN << 1]; inline void Add(int u, int v){ e[++cnt].to = v; e[cnt].next = head[u]; head[u] = cnt; } void dfs_deep(int rt, int father, int depth){ size[rt] = 1; fa[rt] = father; deep[rt] = depth; int max_son = -1; for(register int i = head[rt]; i; i = e[i].next){ int v = e[i].to; if(v == father) continue; dfs_deep(v, rt, depth + 1); size[rt] += size[v]; if(size[v] > max_son){ son[rt] = v; max_son = size[v]; } } } void dfs_top(int rt, int top_fa){ dfn[rt] = ++num; top[rt] = top_fa; if(!son[rt]) return; dfs_top(son[rt], top_fa); for(register int i = head[rt]; i; i = e[i].next){ int v = e[i].to; if(!dfn[v]) dfs_top(v, v); } } struct Segmemt_Tree{ struct Tree{ int l, r; int sum; int lc, rc; //喵啊,可以转化成染色 int lazy; }tr[MAXN << 2]; inline int lson(int rt){ return rt << 1; } inline int rson(int rt){ return rt << 1 | 1; } inline void Pushup(int rt){ tr[rt].lc = tr[lson(rt)].lc; tr[rt].rc = tr[rson(rt)].rc; tr[rt].sum = tr[lson(rt)].sum + tr[rson(rt)].sum; if(tr[lson(rt)].rc != tr[rson(rt)].lc) tr[rt].sum++; } inline void Pushdown(int rt){ if(tr[rt].lazy){ tr[lson(rt)].sum = 0; tr[rson(rt)].sum = 0; tr[lson(rt)].lazy = tr[rt].lazy; tr[rson(rt)].lazy = tr[rt].lazy; tr[lson(rt)].lc = tr[lson(rt)].rc = tr[rt].lazy; tr[rson(rt)].lc = tr[rson(rt)].rc = tr[rt].lazy; tr[rt].lazy = 0; } } void Build(int rt, int l, int r){ tr[rt].l = l; tr[rt].r = r; if(l == r){ tr[rt].sum = 0; tr[rt].lc = tr[rt].rc = l; return; } int mid = (l + r) >> 1; Build(lson(rt), l, mid); Build(rson(rt), mid + 1, r); Pushup(rt); } void Update(int rt, int l, int r, int data){ if(tr[rt].l >= l && tr[rt].r <= r){ tr[rt].sum = 0; tr[rt].lazy = data; tr[rt].lc = tr[rt].rc = data; return; } Pushdown(rt); int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid) Update(lson(rt), l, r, data); if(r > mid) Update(rson(rt), l, r, data); Pushup(rt); } int Query_Sum(int rt, int l, int r){ if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].sum; Pushdown(rt); int ans = 0; int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid) ans += Query_Sum(lson(rt), l, r); if(r > mid) ans += Query_Sum(rson(rt), l, r); if(l <= mid && r > mid){ if(tr[lson(rt)].rc != tr[rson(rt)].lc) ans++; } return ans; } int Query_Time(int rt, int pos){ if(tr[rt].l == tr[rt].r) return tr[rt].lc; Pushdown(rt); int mid = (tr[rt].l + tr[rt].r) >> 1; if(pos <= mid) return Query_Time(lson(rt), pos); else return Query_Time(rson(rt), pos); } }S; void Update_Tree(int x, int y, int data){ while(top[x] != top[y]){ if(deep[top[x]] < deep[top[y]]) swap(x, y); S.Update(1, dfn[top[x]], dfn[x], data); x = fa[top[x]]; } if(deep[x] > deep[y]) swap(x, y); S.Update(1, dfn[x], dfn[y], data); } int Query_Sum_Tree(int x, int y){ int ans = 0; while(top[x] != top[y]){ if(deep[top[x]] < deep[top[y]]) swap(x, y); ans += S.Query_Sum(1, dfn[top[x]], dfn[x]); if(S.Query_Time(1, dfn[top[x]]) != S.Query_Time(1, dfn[fa[top[x]]])) ans++; x = fa[top[x]]; } if(deep[x] > deep[y]) swap(x, y); ans += S.Query_Sum(1, dfn[x], dfn[y]); return ans; } inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int main(){ n = read(); for(register int i = 1; i <= n - 1; i++){ int u, v; u = read(), v = read(); Add(u, v); Add(v, u); } dfs_deep(1, 0, 1); dfs_top(1, 1); S.Build(1, 1, n); times = n; m = read(); for(register int i = 1; i <= m; i++){ int opt, x, y; opt = read(), x = read(), y = read(); if(opt == 1) Update_Tree(x, y, ++times); else printf("%d\n", Query_Sum_Tree(x, y)); } return 0; }
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16600773.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理