「HAOI2016」食物链
题意
现在给你 n n 个物种和 m m 条能量流动关系,求其中的食物链条数。
1 ≤ n ≤ 100000 , 0 ≤ m ≤ 200000 1 ≤ n ≤ 100000 , 0 ≤ m ≤ 200000
题解
拓扑 d p d p 入门题,没什么好讲的。
但是注意要看清题 ,单个生物不算食物链。
代码
「HAOI2016」放棋子
题意
给你一个 N × N N × N 的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放 N N 枚棋子(障碍的位置不能放棋子),要求你放 N N 个棋子也满足每行只有一枚棋子,每列只有一枚棋子的限制,求有多少种方案。
N ≤ 200 N ≤ 200
题解
题意其实就是给你一个障碍排列 { A i } { A i } 求有多少个排列 { P i } { P i } 满足对于 ∀ i ∀ i 都有 A i ≠ P i A i ≠ P i 。
不难发现 A i A i 的顺序是不影响答案的,那么就是错排数了。
利用递推公式 f [ n ] = ( n − 1 ) ( f [ n − 1 ] + f [ n − 2 ] ) f [ n ] = ( n − 1 ) ( f [ n − 1 ] + f [ n − 2 ] ) 和 python
的高精度就可以求解了。(偷懒啦)
代码
「HAOI2016」地图
题意
出题人语文老师 die 了。
有一个 n n 个点 m m 条边的仙人掌,其中每个点有一种颜色种类 a i a i 。
有 q q 次询问,每次给出三个参数 t y , x , y t y , x , y ,表示如果当前 1 → x 1 → x 所有简单路径都封死的情况下,x x 能到达点的颜色编号 a i ≤ y a i ≤ y 且出现次数 mod 2 = t y mod 2 = t y 的颜色有多少种。。
n ≤ 100000 , m ≤ 150000 , Q ≤ 100000 , a i ≤ 10 6 n ≤ 100000 , m ≤ 150000 , Q ≤ 100000 , a i ≤ 10 6
题解
前面那个仙人掌,然后封路。其实就是对应求出圆方树后 x x 的子树。(至于这个圆方树不一定要对于点双建新点,用原来的点就行了)
问题就转化成为多次询问区间中颜色在一段区间内的颜色 ≤ y ≤ y 出现次数为奇/偶的点。
如果做过 Gty的二逼妹子序列 那么就绝对会做这题啦。
考虑莫队,那么插入和删除都要 O ( n √ m ) O ( n m ) 次,询问区间权值种类只有 O ( m ) O ( m ) 次。
如果用线段树维护,瓶颈在于前面插入删除,就变成 O ( n √ m log n ) O ( n m log n ) 应该跑不过。
利用平衡结合的思路,减少前者的复杂度,增加后者的复杂度。不难想到分块可以做到 O ( 1 ) − O ( √ n ) O ( 1 ) − O ( n ) 或者 O ( √ n ) − O ( 1 ) O ( n ) − O ( 1 ) 插入+询问。
那么就可以做到 O ( n √ m + m √ n ) O ( n m + m n ) 啦。
代码
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define pb push_back
using namespace std;
template <typename T> inline bool chkmin (T &a, T b) { return b < a ? a = b, 1 : 0 ; }
template <typename T> inline bool chkmax (T &a, T b) { return b > a ? a = b, 1 : 0 ; }
inline int read () {
int x (0 ) , sgn (1 ) ; char ch (getchar()) ;
for (; !isdigit (ch); ch = getchar ()) if (ch == '-' ) sgn = -1 ;
for (; isdigit (ch); ch = getchar ()) x = (x * 10 ) + (ch ^ 48 );
return x * sgn;
}
void File () {
#ifdef zjp_shadow
freopen ("2062.in" , "r" , stdin);
freopen ("2062.out" , "w" , stdout);
#endif
}
const int N = 1.5e5 + 1e3 ;
int n, m;
vector<int > G[N], E[N];
int dfn[N], lowlink[N], stk[N], top;
void Tarjan (int u, int fa = 0 ) {
static int clk = 0 ;
dfn[u] = lowlink[u] = ++ clk;
stk[++ top] = u;
for (int v : G[u]) if (!dfn[v]) {
Tarjan (v, u);
chkmin (lowlink[u], lowlink[v]);
if (lowlink[v] >= dfn[u]) {
int cur;
do E[u].pb (cur = stk[top --]); while (cur != v);
}
} else if (v != fa) chkmin (lowlink[u], dfn[v]);
}
int efn[N], num[N];
void Dfs_Init (int u, int fa = 0 ) {
static int clk = 0 ;
num[dfn[u] = ++ clk] = u;
for (int v : E[u])
if (v != fa) Dfs_Init (v, u);
efn[u] = clk;
}
int a[N], Hash[N];
int blksz, blkid[N], Beg[N], End[N];
struct Query {
int id, opt, l, r, lim;
} Q[N];
struct Cmp {
inline bool operator () (const Query &lhs, const Query &rhs) {
if (blkid[lhs.l] != blkid[rhs.l]) return blkid[lhs.l] < blkid[rhs.l];
if (lhs.r != rhs.r) return lhs.r < rhs.r;
return lhs.id < rhs.id;
}
};
int sum[N][2 ], times[N];
inline void Insert (int col) {
if (times[col]) -- sum[blkid[col]][times[col] & 1 ];
++ sum[blkid[col]][(++ times[col]) & 1 ];
}
inline void Delete (int col) {
-- sum[blkid[col]][times[col] & 1 ];
if ((-- times[col])) ++ sum[blkid[col]][times[col] & 1 ];
}
int ans[N];
int main () {
File ();
n = read (); m = read ();
For (i, 1 , n)
Hash[i] = a[i] = read ();
sort (Hash + 1 , Hash + n + 1 );
int cnt = unique (Hash + 1 , Hash + n + 1 ) - Hash - 1 ;
For (i, 1 , n)
a[i] = lower_bound (Hash + 1 , Hash + cnt + 1 , a[i]) - Hash;
For (i, 1 , m) {
int u = read (), v = read ();
G[u].pb (v); G[v].pb (u);
}
Tarjan (1 ); Dfs_Init (1 );
int q = read ();
blksz = sqrt (max (n, q) + .5 );
For (i, 1 , max (n, q)) {
blkid[i] = i / blksz + 1 ;
if (blkid[i] != blkid[i - 1 ])
End[blkid[i - 1 ]] = i - 1 , Beg[blkid[i]] = i;
}
End[blkid[max (n, q)]] = max (n, q);
For (i, 1 , q) {
int opt = read (), x = read (), y = read ();
y = upper_bound (Hash + 1 , Hash + cnt + 1 , y) - Hash - 1 ;
Q[i] = (Query) {i, opt, dfn[x], efn[x], y};
}
sort (Q + 1 , Q + q + 1 , Cmp ());
int l = 1 , r = 0 ;
For (i, 1 , q) {
while (r < Q[i].r) Insert (a[num[++ r]]);
while (l > Q[i].l) Insert (a[num[-- l]]);
while (r > Q[i].r) Delete (a[num[r --]]);
while (l < Q[i].l) Delete (a[num[l ++]]);
int lim = Q[i].lim, res = 0 ;
For (j, 1 , blkid[lim] - 1 )
res += sum[j][Q[i].opt];
For (j, Beg[blkid[lim]], lim) if (times[j])
res += (times[j] & 1 ) == Q[i].opt;
ans[Q[i].id] = res;
}
For (i, 1 , q)
printf ("%d\n" , ans[i]);
return 0 ;
}
「HAOI2016」字符合并
题意
有一个长度为 n n 的 01 01 串,你可以每次将相邻的 k k 个字符合并,得到一个新的字符并获得一定分数。得到的新字符和分数由这 k k 个字符确定。你需要求出你能获得的最大分数。
1 ≤ n ≤ 300 , 0 ≤ c i ≤ 1 , w i ≥ 1 , k ≤ 8 1 ≤ n ≤ 300 , 0 ≤ c i ≤ 1 , w i ≥ 1 , k ≤ 8
题解
一开始想到 d p d p 了,又不会转移。。。最近为啥这么多这种情况啊 TAT 看来细节还是不会写。
看到数据范围不难想到一个 区间+状压 d p d p ,令 f l , r , S f l , r , S 为 [ l , r ] [ l , r ] 最后合并成 S S 这个状态的最优答案。
注意到如果 | S | > k | S | > k 一定不优,我们一定会在当前把它合并掉。
那么转移的时候如果 | S | = k | S | = k 那么我们就合并就好啦。
其他的话,转移不需要枚举左右都选了很多个的情况,强制左边选 | S | − 1 | S | − 1 个右边选 1 1 个就好啦,讨论的情况会少很多QAQ
这样的话,复杂度是 O ( n 3 2 k ) O ( n 3 2 k ) 的,可能被卡常。
我们再进行一点小小的优化,你会发现每次合并的时候,很多合并对应的方案是相同的。我们在枚举断点那里每次跳 k − 1 k − 1 然后合并就好啦 qwq
复杂度此时就变成 O ( n 3 2 k k ) O ( n 3 2 k k ) 啦,跑的还挺快的。
总结
最优化转移还是那句话,宜多不宜少。你把很多个更优秀的状态记到劣一点的状态上是不会影响答案的,此时转移会变得容易许多。
然后想一个看起来不对的 d p d p ,猜测一下它能包含所有状态,举一下反例,发举不出,那么此时它就能包含所有可能的状态啦。
代码
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
using ll = long long ;
template <typename T> inline bool chkmin (T &a, T b) { return b < a ? a = b, 1 : 0 ; }
template <typename T> inline bool chkmax (T &a, T b) { return b > a ? a = b, 1 : 0 ; }
inline int read () {
int x (0 ) , sgn (1 ) ; char ch (getchar()) ;
for (; !isdigit (ch); ch = getchar ()) if (ch == '-' ) sgn = -1 ;
for (; isdigit (ch); ch = getchar ()) x = (x * 10 ) + (ch ^ 48 );
return x * sgn;
}
void File () {
#ifdef zjp_shadow
freopen ("2063.in" , "r" , stdin);
freopen ("2063.out" , "w" , stdout);
#endif
}
const int N = 310 ;
const ll inf = 0x3f3f3f3f3f3f3f3f ;
ll f[N][N][1 << 8 ], g[2 ];
char str[N]; int a[N], c[N], w[N];
int main () {
File ();
int n = read (), k = read ();
scanf ("%s" , str + 1 );
For (i, 1 , n) a[i] = str[i] ^ 48 ;
Rep (i, 1 << k) c[i] = read (), w[i] = read ();
For (i, 1 , n) For (j, i, n) Rep (S, 1 << k) f[i][j][S] = -inf;
Fordown (i, n, 1 ) For (j, i, n) {
if (i == j) {
f[i][j][a[i]] = 0 ; continue ;
}
int len = (j - i) % (k - 1 ); if (!len) len = k - 1 ;
for (int mid = j; mid > i; mid -= k - 1 ) Rep (S, 1 << len) {
chkmax (f[i][j][S << 1 ], f[i][mid - 1 ][S] + f[mid][j][0 ]);
chkmax (f[i][j][S << 1 | 1 ], f[i][mid - 1 ][S] + f[mid][j][1 ]);
}
if (len == k - 1 ) {
g[0 ] = g[1 ] = -inf;
Rep (S, 1 << k)
chkmax (g[c[S]], f[i][j][S] + w[S]);
Rep (id, 2 ) f[i][j][id] = g[id];
}
}
ll ans = -inf;
Rep (S, 1 << k)
chkmax (ans, f[1 ][n][S]);
printf ("%lld\n" , ans);
return 0 ;
}
「HAOI2016」找相同字符
可以参考我原来写的 后缀数组小结 ,里面的最后一道例题就是啦。
这个也可以广义 S A M S A M 解决,或者一个串在另外一个 S A M S A M 跑匹配就行了。
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2018-02-13 BZOJ 2683: 简单题(CDQ分治 + 树状数组)