一般思考是,对每个连通块里的全部点减去其中最小点的值,去掉最小点分裂出新的连通块,依次进行,所有减去的值的和为答案。困难的是如何使得连通块分裂。
解决办法为逆向构造连通块,在这个过程中最后留下的总是值最大的点,因此可以令点从大到小排序,依次加入图中。
对于每个新加入的点x,遍历其所有边(x,y),如果y在x之前加入且x与y不连通,说明y所在连通块受到x影响,且还未把该影响更新。利用并查集将x,y合并,并且让y所在集合的根的父亲更新为x,集合的根也更新为x。这样对于每个点u,对答案的贡献值就是b[u]-b[fa[u]]。(显然,根总是上一个该连通图中的最小值点,该点直接受到x的影响,若x非最后的一个值则x又受到该连通图中更小值的影响,从而重现了每次减值时最小值点的消失)
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int T, n, m, fa[maxn], set[maxn], b[maxn], id[maxn], pos[maxn];
int find(int x) {
return set[x] == x ? x : set[x] = find(set[x]);
}
void combine(int x, int y) {
y = find(y);
if (y == x)return;
fa[y] = set[y] = x;
}
bool cmp(int x, int y) {
return b[x] > b[y];
}
int main(void) {
scanf("%d", &T);
while (T--) {
scanf("%d %d", &n, &m);
vector<int>link[maxn];
for (int i = 1; i <= n; i++) {
set[i] = id[i] = i;
fa[i] = 0;
scanf("%d", &b[i]);
}
sort(id + 1, id + 1 + n, cmp);
for (int i = 1; i <= n; i++)pos[id[i]] = i;
while (m--) {
int u, v;
scanf("%d %d", &u, &v);
link[u].push_back(v);
link[v].push_back(u);
}
for (int i = 2; i <= n; i++)
for (int j = 0; j < link[id[i]].size(); j++)
if (pos[link[id[i]][j]] < i)
combine(id[i], link[id[i]][j]);
ll ans = 0;
for (int i = 1; i <= n; i++)
ans += b[i] - b[fa[i]];
printf("%lld\n", ans);
}
return 0;
}
这题就卡在题目没说取模,一般可以用几个常用的如1e9+9,2^64,998244353
#include
#include
using namespace std;
typedef unsigned long long ull;
const int maxn = 2e6 + 5;
ull fib[maxn], A, B, C;
int T, i, len, x, vis[maxn];
ull ksc(ull a, ull b) {
ull res = 0;
while (b) {
if (b & 1)res += a;
a <<= 1;
b >>= 1;
}
return res;
}
int main(void) {
fib[1] = 1; fib[2] = 2;
for (int i = 3; i < maxn; i++)
fib[i] = fib[i - 1] + fib[i - 2];
scanf("%d", &T);
while (T--){
A = B = C = 0;
scanf("%d", &len);
for (int i = 1; i <= len; i++) {
scanf("%d", &x);
if (x)A += fib[i];
}
scanf("%d", &len);
for (int i = 1; i <= len; i++) {
scanf("%d", &x);
if (x)B += fib[i];
}
scanf("%d", &len);
for (int i = 1; i <= len; i++) {
scanf("%d", &x);
vis[i] = x;
if (x)C += fib[i];
}
A = ksc(A, B) - C;
for (int i = 1;; i++) {
if (vis[i])continue;
if (fib[i] == A){
printf("%d\n", i);
break;
}
}
}
return 0;
}
主要是判断点是否在多边形内
#include
#include
#include
using namespace std;
const int maxn = 400 + 5;
const int maxs = 4000010;
int T, sx, sy, x, y, n, m, q, w[maxn][maxn];
int tag[maxn][maxn], inIt[maxn * maxn], POS;
char s[maxs];
inline void umin(int& a, int b) { a > b ? (a = b) : 0; }
inline void umax(int& a, int b) { a < b ? (a = b) : 0; }
int main(void) {
scanf("%d", &T);
while(T--){
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &w[i][j]);
while (q--) {
scanf("%d%d%s", &sx, &sy, s);
int len = strlen(s);
int xl = n, xr = 0, yl = m, yr = 0;
x = sx, y = sy;
for (register int i = 0; i < len; i++) {
if (s[i] == 'L')x--;
if (s[i] == 'R')x++;
if (s[i] == 'D')y--;
if (s[i] == 'U')y++;
umin(xl, x);
umax(xr, x);
umin(yl, y);
umax(yr, y);
}
for (register int i = xl; i <= xr; i++)
for (register int j = yl; j <= yr; j++)
tag[i][j] = 0;
x = sx, y = sy;
for (register int i = 0; i < len; i++) {
if (s[i] == 'L')tag[x][y + 1] = 1, x--;
if (s[i] == 'R')x++, tag[x][y + 1] = 1;
if (s[i] == 'D')y--;
if (s[i] == 'U')y++;
}
POS++;
int ans = 0;
for (register int i = xl + 1; i <= xr; i++)
for (register int j = yl + 1, o = 0; j <= yr; j++) {
o ^= tag[i][j];
if (!o)continue;
if (inIt[w[i][j]] != POS)inIt[w[i][j]] = POS, ans++;
}
/*
向右走,到达目标点,下一步有三种情况:上下右
目标点如果继续向上或者向下垂直延申.则这条路径从下向上的第二个点开始,其值一直都在多边形内
向左走,到达目标点,下一步有三种情况:上下左
则在上一步的点,其情况和向右走的目标点一样.
称类似向右走的目标点为x点,每次在x点的上方一点做个标记
(考虑最左的边是不包含在范围内的,因此一定要保证标记的左方的垂直方向上有点)
从下向上,若路经过了第一个标记,即开始遇到了在范围内的点,遇到第二次则走出了范围
因此如果每次右走在目标点上方作个标记,每次左走在走前点的上方做个标记
则在范围内从下向上遍历,如果遇到了标记的次数为奇数,说明目前的点在范围内,若为偶数,则不在范围内
*/
printf("%d\n", ans);
}
}
return 0;
}
稍微剪下枝,把item数目为0的点尽量跳过,否则每次遇到会增加正好一倍的dfs,更好的是先根据item数目排序,剪枝效果最好。
#include
#include
using namespace std;
typedef long long ll;
int T, n, k, cnt[55], nxt[55];
ll ans;
struct node{
int a, b, c, d;
node(int _a = 0, int _b = 0, int _c = 0, int _d = 0) :a(_a), b(_b), c(_c), d(_d) {}
}p[55][55];
void dfs(int depth, int a, int b, int c, int d) {
if (depth > k) {
ll tmp = 1LL * a * b * c * d;
if (tmp > ans)ans = tmp;
return;
}
if (!cnt[depth]) {
dfs(nxt[depth], a, b, c, d);
return;
}
for (int i = 0; i < cnt[depth]; i++)
dfs(depth + 1, a + p[depth][i].a, b + p[depth][i].b, c + p[depth][i].c, d + p[depth][i].d);
}
int main(void) {
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
memset(cnt, 0, sizeof(cnt));
while (n--) {
int t, a, b, c, d;
scanf("%d %d %d %d %d", &t, &a, &b, &c, &d);
p[t][cnt[t]++] = node(a, b, c, d);
}
int x = k + 1;
for (int i = k; i >= 1; i--) {
nxt[i] = x;
if (cnt[i])x = i;
}//保证每个为0的t,下一个接着就是不为0的一层
ans = 0;
dfs(1, 100, 100, 100, 100);
printf("%lld\n", ans);
}
return 0;
}
很容易知道要求A的区间串与B串的最长公共子序列长度为len,答案就是R-L+1+m-len
题解是初始化一个g[i][j]数组表示在串A[i…n]中字符j第一次出现的下标
利用该数组DP求出区间与B串的最长公共子序列长度
设数组f[i][j]表示B[1…i]与A[L…R]有长度为j的公共子序列的A[L…R]中最小前缀的下标,显然可以初始化f[0][0]=L-1,将i从0开始遍历,确定已知f[i][j]的正确值,利用f[i][j]推出f[i+1][j]和f[i+1][j+1]。
如果f[i][j]#include