浅浅的记录一下最近刷到的略难题,虽然但是黑题怎么可能刷得到。
又是最短路相关,仍然考虑魔改 dijkstra。
我们发现这个柿子等价于任意选择一条边不选任意一条边翻倍得到的最小距离。你可以贪心证明或者看错题意大法得到这个性质。
那么考虑拆点,每个点 $u$ 还要维护你是否没选过一条边和你是否翻倍过一条边。下一条边就直接往后直接走或不算贡献或翻倍讨论一下就好。
然后大力 dijkstra 即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
int n, m;
vector<pii> a[800020];
ll dis[800020];
bool vis[800020];
void dijk(int u)
{
priority_queue<pii, vector<pii>, greater<pii>> q;
q.push({0, u});
dis[u] = 0;
while (!q.empty())
{
u = q.top().second;
q.pop();
if (vis[u])
continue;
vis[u] = 1;
int x = u & 2;
int y = u & 1;
int z = u >> 2;
for (auto p : a[z])
{
int v = p.second;
ll w = p.first;
v += x + y;
if (dis[v] > dis[u] + w)
q.push({dis[v] = dis[u] + w, v});
if (!x && dis[v | 2] > dis[u])
q.push({dis[v | 2] = dis[u], v | 2});
if (!y && dis[v | 1] > dis[u] + (w << 1))
q.push({dis[v | 1] = dis[u] + (w << 1), v | 1});
}
}
}
int main()
{
memset(dis, 0x3f, sizeof(dis));
cin >> n >> m;
while (m--)
{
int u, v, w;
cin >> u >> v >> w;
a[u].push_back({w, v << 2});
a[v].push_back({w, u << 2});
}
dijk(1 << 2);
for (int i = 2; i <= n; i++)
cout << min(dis[i << 2], dis[i << 2 | 3]) << " ";
cout << endl;
return 0;
}
···
- [CF 113D Museum](https://codeforces.com/problemset/problem/113/D)
把点拆开存到矩阵里,然后迭代 17 次,更小 WA,过大 TLE。
```cpp
#include <bits/stdc++.h>
using namespace std;
struct Matrix
{
double a[500][500];
} mtx;
int n, m;
int s, t;
double p[30];
int id[30][30];
vector<int> a[30];
int loveher;
Matrix operator*(const Matrix &x, const Matrix &y)
{
Matrix z;
for (int i = 1; i <= loveher; i++)
for (int j = 1; j <= loveher; j++)
z.a[i][j] = 0;
for (int i = 1; i <= loveher; i++)
for (int j = 1; j <= loveher; j++)
for (int k = 1; k <= loveher; k++)
z.a[i][j] += x.a[i][k] * y.a[k][j];
return z;
}
int main()
{
cin >> n >> m >> s >> t;
while (m--)
{
int u, v;
cin >> u >> v;
a[u].push_back(v);
a[v].push_back(u);
}
for (int i = 1; i <= n; i++)
cin >> p[i];
// for (int i = 1; i <= n; i++)
// cout << p[i] << " \n"[i == n];
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++)
id[i][j] = id[j][i] = ++loveher;
for (int i = 1; i <= n; i++)
mtx.a[id[i][i]][id[i][i]] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
mtx.a[id[i][j]][id[i][j]] += p[i] * p[j];
// cout << id[i][j] << " " << p[i] * p[j] << endl;
for (int k : a[i])
{
mtx.a[id[i][j]][id[k][j]] += (1.0 - p[i]) / a[i].size() * p[j];
for (int t : a[j])
mtx.a[id[i][j]][id[k][t]] += (1.0 - p[i]) / a[i].size() * (1.0 - p[j]) / a[j].size();
}
for (int k : a[j])
mtx.a[id[i][j]][id[i][k]] += p[i] * (1.0 - p[j]) / a[j].size();
}
}
for (int i = 1; i <= 20; i++)
mtx = mtx * mtx;
for (int i = 1; i <= n; i++)
printf("%.7f ", mtx.a[id[s][t]][id[i][i]]);
return 0;
}
最暴力的 $f_{u,v,x,y}$ 表示左上 $(u,v)$ 右下 $(x,y)$ 的答案。
模拟赛场上打得类似这种,搜索过多爆栈而死,排名 -= inf,警钟长鸣。
注意到最多只有 $\log_2 n+\log_2 m=\log_2 n\times m$ 刀。
考虑 $f_{k,x,y,j}$ 表示切下 $k$ 刀后 $x$ 和 $y$ 行从 $j$ 开始能走到哪里。典型的答案与状态互换。
然后就很好转移啦。第一维可以直接压掉。
#include <bits/stdc++.h>
using namespace std;
template <typename T>
void gmx(T &x, T y) { x = max(x, y); }
int n, m;
char mp[520][520];
int s[520][520];
int f[2][320][320][320];
int S(int u, int v, int x, int y) { return s[x][y] - s[u - 1][y] - s[x][v - 1] + s[u - 1][v - 1]; }
bool in(int x, int y) { return 1 <= x && x <= n && 1 <= y && y <= m; }
bool same(int u, int v, int x, int y)
{
int sum = S(u, v, x, y);
return !(sum ^ (x - u + 1) * (y - v + 1)) || !sum;
}
int bs_mx(int op, int i, int j, int k)
{
int L = i, R = j, mid;
int ret = 0;
while (L <= R)
{
mid = L + R >> 1;
int p = f[op][i][mid][k];
int q = f[op][mid + 1][j][k];
gmx(ret, min(p, q));
if (p > q)
L = mid + 1;
else
R = mid - 1;
}
return ret;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> mp[i][j];
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + (mp[i][j] == '#');
}
}
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
for (int k = 1; k <= m; k++)
{
int L = k - 1, R = m, mid;
int &t = f[0][i][j][k];
while (L <= R)
{
mid = L + R >> 1;
if (same(i, k, j, mid))
L = mid + 1, t = mid;
else
R = mid - 1;
}
}
}
}
int ans = 0;
int tmp = 0, lst = 1;
while (f[tmp][1][n][1] < m)
{
ans++;
tmp ^= 1;
lst ^= 1;
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
for (int k = 1; k <= m; k++)
{
int p = f[lst][i][j][k];
f[tmp][i][j][k] = (p ^ m ? max(f[lst][i][j][p + 1], bs_mx(lst, i, j, k)) : m);
}
}
}
}
cout << ans << endl;
return 0;
}
dp。
https://www.luogu.com.cn/discuss/656534
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef __float128 fl;
const int p = 1000000007;
int n, m;
ll l;
ll a[120];
bool lst = 0, cur = 1;
template <typename T>
void out(T ans)
{
if (ans + 1e-14 >= 1)
{
cout << "1." << string(m, '0') << endl;
return;
}
cout << "0.";
ans *= 10;
for (int i = 1; i <= m; ++i)
{
cout << (int)(ans + (m == i) * 0.5);
ans = (ans - (int)ans) * 10;
}
}
int main()
{
cin >> n >> l >> m;
for (int i = 1; i <= n; i++)
a[i] = i;
if (n == 1)
return cout << 1 << endl, 0;
if (m <= 8)
{
ld f[2][120][5020][5] = {};
f[0][0][0][0] = 1;
for (int i = 1; i <= n; i++)
{
memset(f[cur], 0, sizeof(f[cur]));
for (int j = 0; j <= n; j++)
{
for (int k = 0; k <= l; k++)
{
for (int op = 0; op < 3; op++)
{
ll x = 2 * j - op;
ll y = x * (a[i] - a[i - 1]);
if (!f[lst][j][k][op] || x < 0 || y + k >= l)
continue;
f[cur][j][k + y][op + 1] += f[lst][j][k][op] * (2 - op) / i;
f[cur][j + 1][k + y][op + 1] += f[lst][j][k][op] * (2 - op) / i;
f[cur][j][k + y][op] += f[lst][j][k][op] * x / i;
f[cur][j + 1][k + y][op] += f[lst][j][k][op] * (x + 1 - j) / i;
if (!j)
continue;
f[cur][j - 1][k + y][op] += f[lst][j][k][op] * (j - 1) / i;
}
}
}
lst ^= 1, cur ^= 1;
}
ld ans = 0;
for (int i = 0; i < l; i++)
ans += f[lst][1][i][2];
out(1 - ans);
}
else
{
fl f[2][120][5020][5] = {};
f[0][0][0][0] = 1;
for (int i = 1; i <= n; i++)
{
memset(f[cur], 0, sizeof(f[cur]));
for (int j = 0; j <= n; j++)
{
for (int k = 0; k <= l; k++)
{
for (int op = 0; op < 3; op++)
{
ll x = 2 * j - op;
ll y = x * (a[i] - a[i - 1]);
if (!f[lst][j][k][op] || x < 0 || y + k >= l)
continue;
f[cur][j][k + y][op + 1] += f[lst][j][k][op] * (2 - op) / i;
f[cur][j + 1][k + y][op + 1] += f[lst][j][k][op] * (2 - op) / i;
f[cur][j][k + y][op] += f[lst][j][k][op] * x / i;
f[cur][j + 1][k + y][op] += f[lst][j][k][op] * (x + 1 - j) / i;
if (!j)
continue;
f[cur][j - 1][k + y][op] += f[lst][j][k][op] * (j - 1) / i;
}
}
}
lst ^= 1, cur ^= 1;
}
fl ans = 0;
for (int i = 0; i < l; i++)
ans += f[lst][1][i][2];
out(1 - ans);
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int p = 1000000007;
int n;
ll l;
ll a[120];
ll f[2][120][1020][5];
bool lst = 0, cur = 1;
int main()
{
cin >> n >> l;
for (int i = 1; i <= n; i++)
cin >> a[i];
if (n == 1)
return cout << 1 << endl, 0;
sort(a + 1, a + n + 1);
f[0][0][0][0] = 1;
for (int i = 1; i <= n; i++)
{
memset(f[cur], 0, sizeof(f[cur]));
for (int j = 0; j <= n; j++)
{
for (int k = 0; k <= l; k++)
{
for (int op = 0; op < 3; op++)
{
ll x = 2 * j - op;
ll y = x * (a[i] - a[i - 1]);
if (!f[lst][j][k][op] || x < 0 || y + k > l)
continue;
(f[cur][j][k + y][op + 1] += f[lst][j][k][op] * (2 - op) % p) %= p;
(f[cur][j + 1][k + y][op + 1] += f[lst][j][k][op] * (2 - op) % p) %= p;
(f[cur][j][k + y][op] += f[lst][j][k][op] * x % p) %= p;
(f[cur][j + 1][k + y][op] += f[lst][j][k][op] * (x + 1 - j)) %= p;
if (!j)
continue;
(f[cur][j - 1][k + y][op] += f[lst][j][k][op] * (j - 1) % p) %= p;
}
}
}
lst ^= 1, cur ^= 1;
}
ll ans = 0;
for (int i = 0; i <= l; i++)
(ans += f[lst][1][i][2]) %= p;
cout << ans << endl;
return 0;
}
好久没记了。
受不了了一拳把模拟赛干爆!受不了了一拳把模拟赛干爆!受不了了一拳把模拟赛干爆!
首先把结论搞出来,$\sum \limits_{i=1}^n\sum \limits_{j=1}^{i}\lvert a_i-a_j\rvert$。
然后数据结构维护。
typedef long long ll;
const int V = 1000000000;
const int p = 998244353;
int n, q;
int rt;
int a[300020];
struct Node
{
ll sum, tot, siz;
int lc, rc;
} Tree[50000020];
#define lc Tree[rt].lc
#define rc Tree[rt].rc
int cnt;
void add(int d, int val, int &rt, int l = 1, int r = V)
{
if (!rt)
rt = ++cnt;
if (!(l ^ r))
return (Tree[rt].tot += 1LL * l * val) %= p, (Tree[rt].siz += val) %= p, void();
int mid = l + r >> 1;
if (d <= mid)
add(d, val, lc, l, mid);
else
add(d, val, rc, mid + 1, r);
Tree[rt].sum = ((Tree[lc].sum - Tree[lc].tot * Tree[rc].siz) % p + p +
(Tree[rc].sum + Tree[rc].tot * Tree[lc].siz) % p + p) %
p;
// assert(Tree[rt].sum >= 0);
Tree[rt].tot = (Tree[lc].tot + Tree[rc].tot) % p;
Tree[rt].siz = (Tree[lc].siz + Tree[rc].siz) % p;
}
int main()
{
cin >> n >> q;
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
for (int i = 1; i <= n; i++)
add(a[i], 1, rt);
while (q--)
{
int x, y;
scanf("%d%d", &x, &y);
add(a[x], -1, rt);
add(a[x] = y, 1, rt);
printf("%lld\n", Tree[rt].sum * (p + 1 >> 1) % p);
}
return 0;
}
看注释。
#include <bits/stdc++.h>
using namespace std;
int n, flag;
int pos1st;
long long ans, x, pos, val, k;
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> x;
if (flag == 0)
{
if (x == -1)
{
ans++;
flag = 1;
pos1st = i;
}
else
{
ans++;
flag = 2;
pos1st = i;
pos = i;
val = x;
}
}
else if (flag == 1)
{
if (x == -1)
{
}
else
{
flag = 2;
pos = i;
val = x;
}
}
else if (flag == 2)
{
if (x == -1)
{
}
else
{
if ((x - val) % (i - pos) == 0 && val - (x - val) / (i - pos) * (pos - pos1st) > 0)
{
flag = 3;
k = (x - val) / (i - pos);
}
else
{
ans++;
flag = 2;
pos1st = i;
pos = i;
val = x;
}
}
}
else if (flag == 3)
{
if (x == -1)
{
if (val + (i - pos) * k > 0)
{
}
else
{
ans++;
flag = 1;
pos1st = i;
}
}
else
{
if (val + (i - pos) * k == x)
{
}
else
{
ans++;
flag = 2;
pos1st = i;
pos = i;
val = x;
}
}
}
}
cout << ans << endl;
return 0;
}
/*
pos1st 当前等差数列第一项在哪里
pos 不是-1的那一项下标是什么
val 不是-1的那一项值是多少
k 最后一个等差数列的公差是多少
flag
0这是第一个数字
1最后一个等差数列全是-1
2最后一个等差数列只有1个不是-1
3最后一个等差数列有2个不是-1
*/
最小值不够小调了一个下午自闭了,不想写笔记了。
#include <bits/stdc++.h>
using namespace std;
template <typename T>
void gmx(T &x, T y) { x = max(x, y); }
int n;
int a[6020];
int ans;
int x, y;
int f[2020][2020];
int mx[2020];
int b[5];
struct node
{
int x, y, z;
};
int main()
{
memset(f, 128, sizeof(f));
memset(mx, 128, sizeof(mx));
mx[0] = 0;
cin >> n;
for (int i = 1; i <= 3 * n; i++)
cin >> a[i];
f[a[1]][a[2]] = f[a[2]][a[1]] = mx[a[1]] = mx[a[2]] = 0;
for (int i = 3; i + 2 <= 3 * n; i += 3)
{
if (a[i] == a[i + 1] && a[i + 1] == a[i + 2])
{
ans++;
continue;
}
b[1] = a[i], b[2] = a[i + 1], b[3] = a[i + 2];
vector<node> v;
for (int j = 1; j <= 3; j++)
{
for (int k = 1; k <= 3; k++)
{
if (!(j ^ k))
continue;
int x = b[j], y = b[k], z = (b[j] ^ b[k] ^ b[1] ^ b[2] ^ b[3]);
// cout << x << " " << y << " " << z << endl;
for (int t = 1; t <= n; t++)
{
int cur = mx[t];
if (!(y ^ z))
gmx(cur, f[t][y] + 1);
v.push_back({t, x, cur});
}
v.push_back({x, y, max(mx[0], f[z][z] + 1)});
}
}
for (auto j : v)
{
int x = j.x, y = j.y, z = j.z;
gmx(f[x][y], z);
gmx(f[y][x], z);
gmx(mx[x], z);
gmx(mx[y], z);
gmx(mx[0], z);
}
}
int fmx = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
gmx(fmx, f[i][j] + (i == j) * (j == a[3 * n]));
cout << ans + fmx << endl;
return 0;
}
为啥我觉得这题难在判是否有解啊。
判是否有解题意可以转化为是否存在 $b$ 使得 $\sum_{i=1}\limits^{n}a_i\times k^{-b_i}=1$。
状态转移考虑一个经典问题:$0\leq x_1\leq x_2\leq x_3\leq\dots\leq x_{n-1}\leq x_n$,且 $\sum x_i=m$。
这个转移当 $x_1=1$ 时有 $f_{n,m}\gets f_{n,m-n}$。
回归这个问题你考虑 $f_{mask,s}$ 选集合为 $mask$ 和为 $s$ 是否可行。
那么当 $f_{mask,s}=\text{True}$ 时,有 $f_{mask\cup \{i\},s+a_i}=\text{True},f_{mask,\frac{s}{k}}=\text{True}$。
然后我们 bitset 优化一下就可以判出是否有解了。
判出了是否有解之后我们找一下每个数字应该在第几层合并即可,可以用大根堆维护。
int main()
{
cin >> n >> k;
m = (1 << n) - 1;
for (int i = 0; i < n; i++)
cin >> a[i], sum += a[i];
f[0][0] = 1;
for (int i = 0; i <= m; i++)
{
for (int j = sum / k; j >= 0; j--)
if (f[i][j * k])
f[i][j] = 1;
for (int j = 0; j < n; j++)
if (!(i >> j & 1))
f[i | (1 << j)] |= f[i] << a[j];
}
if (!f[m][1])
return puts("NO"), 0;
puts("YES");
int cnt = 1, dist = 0;
while (m)
{
if (cnt * k <= sum && f[m][cnt * k])
{
dist++, cnt *= k;
continue;
}
for (int i = 0; i < n; i++)
{
if ((m >> i & 1) && cnt >= a[i] && f[m ^ (1 << i)][cnt - a[i]])
{
dis[i] = dist;
m ^= (1 << i);
cnt -= a[i];
break;
}
}
}
for (int i = 0; i < n; i++)
q.push({dis[i], a[i]});
while (q.size() > 1)
{
pii u = q.top();
q.pop();
pii v = q.top();
pii w = u;
q.pop();
cout << u.second << " " << v.second << endl;
w.second += v.second;
while (w.second % k == 0)
{
w.second /= k;
w.first--;
}
q.push(w);
}
return 0;
}
你考虑前面放 $1\sim 100$,后面手玩几个小的看一下答案与你插入字符的关系,然后乱搞即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int ans[520], top;
ll n;
int tmp;
int main()
{
cin >> n;
for (int i = 1; i <= 100; i++)
ans[++top] = i;
while ((1LL << tmp + 1) - 1 <= n)
tmp++;
for (int i = 1; i <= tmp; i++)
ans[++top] = i * 2;
n -= (1LL << tmp) - 1;
// cout << n << endl;
for (int i = tmp - 1; i >= 0; i--)
if (n >> i & 1)
ans[++top] = i * 2 + 1;
cout << top << endl;
for (int i = 1; i <= top; i++)
cout << ans[i] << " ";
cout << endl;
return 0;
}
用 $f_{i,j}$ 表示已经排好了 $[1,i]$,最后一个没有改位置的值是 $j$ 的代价。
- 若 $a_i<j$,那么应该是需要往前挪,$f_{i,j}\gets f_{i-1,j}+B$
- 若 $a_i>j$,那么应该是需要往后挪,$f_{i,j}\gets f_{i-1,j}+A$
- 若 $a_i>j$,可以选择不动让后面的往前把自己推走,$f_{i,a_i}\gets f_{i-1,j}$
template <typename T>
void gmn(T &x, T y) { x = min(x, y); }
typedef long long ll;
int n;
ll A, B;
ll a[5020];
ll f[5020][5020];
int main()
{
memset(f, 0x3f, sizeof(f));
cin >> n >> A >> B;
for (int i = 1; i <= n; i++)
cin >> a[i];
f[0][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n; j++)
if (a[i] < j)
gmn(f[i][j], f[i - 1][j] + B);
else
gmn(f[i][j], f[i - 1][j] + A), gmn(f[i][a[i]], f[i - 1][j]);
ll ans = 0x3f3f3f3f3f3f3f3f;
for (int i = 1; i <= n; i++)
ans = min(ans, f[n][i]);
cout << ans << endl;
return 0;
}
$f_{l,r,cnt}$ 表示 $[l,r]$ 前有 $cnt$ 个 $a_l$ 颜色的珠子所耗费步数。
int dp(int l, int r, int cnt)
{
if (f[l][r][cnt] != -1)
return f[l][r][cnt];
if (l > r)
return f[l][r][cnt] = 0;
if (l == r)
return f[l][r][cnt] = m - cnt - 1;
int &ans = f[l][r][cnt] = (cnt == m - 1 ? dp(l + 1, r, 0) : (cnt < m - 1 ? dp(l, r, cnt + 1) + 1 : inf));
for (int k = l; k < r; k++)
if (a[l] == a[k + 1])
ans = min(ans, dp(l + 1, k, 0) + dp(k + 1, r, min(m - 1, cnt + 1)));
return ans;
}
int lowbit(int x) { return x & -x; }
const int p = 1000000007;
ll f[2][1 << 20];
int n, m, k;
int u, v, w;
int a[50][50];
int main()
{
n = read() - 1, m = read(), k = read();
memset(a, -1, sizeof(a));
for (int i = 0; i < k; i++)
{
u = read(), v = read(), w = read();
a[u][v] = w;
}
f[0][0] = 1;
int tmp = 0, lst = 1;
for (int i = 1; i <= m; i++)
{
for (int j = 0; j < n; j++)
{
tmp ^= 1, lst ^= 1;
memset(f[tmp], 0, sizeof(f[tmp]));
for (int msk = 0; msk < (1 << n); msk++)
{
if ((msk >> j) & 1)
{
if (a[i][j + 1] ^ 0)
(f[tmp][msk] += f[lst][msk]) %= p;
}
else
{
if (a[i][j + 1] ^ 1)
(f[tmp][msk] += f[lst][msk]) %= p;
if (a[i][j + 1] ^ 0)
(f[tmp][msk - (lowbit(msk >> j) << j) + (1 << j)] += f[lst][msk]) %= p;
}
}
}
}
int ans = 0;
for (int i = 0; i < (1 << n); i++)
(ans += f[tmp][i]) %= p;
cout << ans << endl;
return 0;
}
考虑构造 $[4,5,7,9,11,13,17,19,23]$。
此时乘积 $>10^9$,和 $<110$。
然后 CRT 求解。
const ll a[] = {0, 4, 5, 7, 9, 11, 13, 17, 19, 23};
const ll s[] = {0, 4, 9, 16, 25, 36, 49, 66, 85, 108};
const ll p = 1338557220;
const ll n = 9;
ll cnt;
ll b[120];
ll N;
int main()
{
cout << 108 << endl;
for (int i = 1; i <= n; i++)
{
while (++cnt < s[i])
cout << cnt + 1 << " ";
cout << s[i - 1] + 1 << " ";
}
cout << endl;
cnt = 0;
for (int i = 1; i <= n; i++)
{
while (++cnt < s[i])
cin >> b[i];
cin >> b[i];
b[i] -= s[i - 1] + 1;
}
for (int i = 1; i <= n; i++)
{
int tmp = 0;
for (int j = 1; j < a[i]; j++)
{
if (p / a[i] * j % a[i] == 1)
{
tmp = p / a[i] * j;
break;
}
}
(N += tmp * b[i]) %= p;
}
cout << N + 1 << endl;
return 0;
}
我们可以把题意转化为每次操作有 $\frac{1}{2}$ 的概率,求最后期望逆序对数。最后答案乘上 $2^Q$ 即可。
费马小定理永远的神,我们直接用 $2^{p-2}$ 当逆元就好。const int inv = qpow(2, p - 2);
。
const int p = 1000000007;
int n, Q;
int l, r;
ll a[3020];
ll f[3020][3020];
ll qpow(ll x, ll y)
{
ll ret = 1;
for (; y; y >>= 1)
{
if (y & 1)
(ret *= x) %= p;
(x *= x) %= p;
}
return ret;
}
const int inv = qpow(2, p - 2);
int main()
{
cin >> n >> Q;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
f[i][j] = a[i] < a[j];
for (int i = 1; i <= Q; i++)
{
cin >> l >> r;
f[l][r] = f[r][l] = (f[l][r] + f[r][l]) * inv % p;
for (int j = 1; j <= n; j++)
{
if (j ^ l && j ^ r)
{
f[l][j] = f[r][j] = (f[l][j] + f[r][j]) * inv % p;
f[j][l] = f[j][r] = (f[j][l] + f[j][r]) * inv % p;
}
}
}
ll ans = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
(ans += f[i][j]) %= p;
(ans *= qpow(2, Q)) %= p;
cout << ans << endl;
return 0;
}
考虑 $f_{i,j,k}$ 表示前 $i$ 个学生,有 $j$ 组还没定下来,总贡献为 $k$。
然后我们分类讨论当前学生的状态:
- 自己一组 $f_{i+1,j,k+tmp}\gets f_{i+1,j,k+tmp}+f_{i,j,k}$
- 新开一组 $f_{i+1,j+1,k+tmp}\gets f_{i+1,j+1,k+tmp}+f_{i,j,k}$
- 结束一组 $f_{i+1,j-1,k+tmp}\gets f_{i+1,j-1,k+tmp}+j\times f_{i,j,k}$
- 延续一组 $f_{i+1,j,k+tmp}\gets f_{i+1,j,k+tmp}+j\times f_{i,j,k}$
其中 $tmp$ 为 $j\times (a_{i+1}-a_i)$。
注意新开一组和结束一组的边界。
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1);
f[1][0][0] = f[1][1][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= i; j++)
{
int tmp = j * (a[i + 1] - a[i]);
for (int k = 0; k <= m - tmp; k++)
{
(f[i + 1][j][k + tmp] += f[i][j][k]) %= p;
if (j ^ n)
(f[i + 1][j + 1][k + tmp] += f[i][j][k]) %= p;
if (j ^ 0)
(f[i + 1][j - 1][k + tmp] += 1LL * j * f[i][j][k] % p) %= p;
(f[i + 1][j][k + tmp] += 1LL * j * f[i][j][k] % p) %= p;
}
}
}
int ans = 0;
for (int i = 0; i <= m; i++)
(ans += f[n][0][i]) %= p;
cout << ans << endl;
碰到一个不满足的就往外扔。扔的时候改变自己的编号。
void getOut(int u)
{
int cnt = 0;
for (int v : a[u])
if (!(team[u] ^ team[v]))
cnt++;
if (cnt > 1)
{
team[u] ^= 1;
for (int v : a[u])
getOut(v);
}
}
$(^n_3)-\sum (^{d_i}_2)$ 即为答案,扫描线可以做到 $\mathcal O(n \log n)$。
要注意先离散化。非 + - * 的线段树要单独判 tag。
struct SegTree
{
int L, R;
ll sum;
bool tag;
} Tree[400020];
void pushdown(int id, int L, int R)
{
if (Tree[id].tag)
{
int mid = L + R >> 1;
Tree[id << 1].tag ^= 1;
Tree[id << 1 | 1].tag ^= 1;
Tree[id << 1].sum = mid - L + 1 - Tree[id << 1].sum;
Tree[id << 1 | 1].sum = R - mid - Tree[id << 1 | 1].sum;
Tree[id].tag = 0;
}
}
void build(int id, int L, int R)
{
Tree[id].L = L;
Tree[id].R = R;
Tree[id].tag = Tree[id].sum = 0;
if (L == R)
return;
int mid = L + R >> 1;
build(id << 1, L, mid);
build(id << 1 | 1, mid + 1, R);
Tree[id].sum = Tree[id << 1].sum + Tree[id << 1 | 1].sum;
}
ll query(int id, int L, int R, int qL, int qR)
{
if (qL <= L && R <= qR)
return Tree[id].sum;
pushdown(id, L, R);
int mid = L + R >> 1;
ll ans = 0;
if (Tree[id << 1].R >= qL)
ans += query(id << 1, L, mid, qL, qR);
if (Tree[id << 1 | 1].L <= qR)
ans += query(id << 1 | 1, mid + 1, R, qL, qR);
return ans;
}
void add(int id, int L, int R, int qL, int qR)
{
if (qL <= L && R <= qR)
return Tree[id].sum = R - L + 1 - Tree[id].sum, Tree[id].tag ^= 1, void();
pushdown(id, L, R);
int mid = L + R >> 1;
if (Tree[id << 1].R >= qL)
add(id << 1, L, mid, qL, qR);
if (Tree[id << 1 | 1].L <= qR)
add(id << 1 | 1, mid + 1, R, qL, qR);
Tree[id].sum = Tree[id << 1].sum + Tree[id << 1 | 1].sum;
}
int main()
{
n = read(), m = read();
for (int i = 1; i <= n; i++)
a[i] = b[i] = read(), c[i] = -b[i];
sort(b + 1, b + n + 1);
sort(c + 1, c + n + 1);
for (int i = 1; i <= n; i++)
a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
for (int i = 1; i <= m; i++)
p[i].first = lower_bound(b + 1, b + n + 1, read()) - b, p[i].second = n - (lower_bound(c + 1, c + n + 1, -read()) - c) + 1;
for (int i = 1; i <= m; i++)
{
if (p[i].first > p[i].second)
continue;
Left[p[i].first].push_back(p[i].second);
Right[p[i].second].push_back(p[i].first);
}
ans = 1LL * n * (n - 1) * (n - 2) / 6;
build(1, 1, n);
for (int i = 1; i <= n; i++)
{
for (int j : Left[i])
add(1, 1, n, i, j);
ll tmp = query(1, 1, n, 1, n) - query(1, 1, n, i, i);
ans -= tmp * (tmp - 1) >> 1;
add(1, 1, n, i, i);
for (int j : Right[i])
add(1, 1, n, j, i);
}
cout << ans << endl;
return 0;
}
咋是个紫?
对角线不相交显然是选出来的行列之和一奇一偶。
然后考虑前缀和,$f_{i,j,0}$ 表示左上到右下,$f_{i,j,0}$ 表示右上到左下。
转移 $f_{i,j,0}\gets f_{i-1,j-1,0}+a_{i,j},f_{i,j,1}\gets f_{i-1,j+1,1}+a_{i,j}$。
查询就找到对应的结束位置即可。
ll S(int x, int y)
{
int p = min(n - x, n - y), q = min(n - x, y - 1);
return f[x + p][y + p][0] + f[x + q][y - q][1] - a[x][y];
}
然后就做完了。
typedef long long ll;
int n;
ll f[2020][2020][2];
ll a[2020][2020];
int A, B, C, D;
ll mxA, mxB;
ll S(int x, int y)
{
int p = min(n - x, n - y), q = min(n - x, y - 1);
return f[x + p][y + p][0] + f[x + q][y - q][1] - a[x][y];
}
void F(int x, int y)
{
ll tmp = S(x, y);
if (tmp >= mxA)
A = x, B = y, mxA = tmp;
}
void G(int x, int y)
{
ll tmp = S(x, y);
if (tmp >= mxB)
C = x, D = y, mxB = tmp;
}
int main()
{
n = read();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
f[i][j][1] = f[i][j][0] = a[i][j] = read();
f[i][j][0] += f[i - 1][j - 1][0];
f[i][j][1] += f[i - 1][j + 1][1];
}
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (i + j & 1)
G(i, j);
else
F(i, j);
cout << mxA + mxB << endl;
cout << A << " " << B << " " << C << " " << D << endl;
return 0;
}
根据裴蜀定理,本题可以转化为选择若干个 $l_i$ 使得 $\sum c_i$ 最小。
考虑 STL 优化暴力 DP,掌握 STL 后比较简单。
cin >> n;
for (int i = 1; i <= n; i++)
cin >> l[i];
for (int i = 1; i <= n; i++)
cin >> c[i], f[l[i]] = (!f[l[i]] ? c[i] : min(f[l[i]], 1LL * c[i]));
for (int i = 1; i <= n; i++)
{
for (auto g : f)
{
int G = __gcd(l[i], g.first);
f[G] = !f[G] ? f[g.first] + c[i] : min(f[G], f[g.first] + c[i]);
}
}
cout << (f[1] ? f[1] : -1) << endl;
或者转化为最短路,边权即为 $c_i$,邻居为 $\gcd(u,l_i)$。
void dijk()
{
f[0] = 0;
q.push(make_pair(0, 0));
while (!q.empty())
{
int u = q.top().second;
q.pop();
if (vis[u])
continue;
vis[u] = 1;
for (int i = 1; i <= n; i++)
{
int v = __gcd(l[i], u);
if (u == v)
continue;
if (!f.count(v) || f[v] > f[u] + c[i])
{
f[v] = f[u] + c[i];
q.push(make_pair(f[v], v));
}
}
}
}
最短路计数,但是 dijkstra。
void dijk()
{
memset(dis, 0x3f, sizeof(dis));
priority_queue<pli, vector<pli>, greater<pli>> q;
q.push({0, 1});
dis[1] = 0;
while (!q.empty())
{
int u = q.top().second;
q.pop();
if (vis[u])
continue;
vis[u] = 1;
for (auto p : a[u])
{
int v = p.second;
ll w = p.first;
if (dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
q.push({dis[v], v});
cnt[v] = 1;
}
else if (!(dis[v] ^ dis[u] + w))
{
cnt[v]++;
}
}
}
}
dijk();
int ans = 0;
for (int i = 1; i <= k; i++)
{
int v = special[i].first;
ll w = special[i].second;
if (dis[v] < w)
ans++;
if (dis[v] == w && cnt[v] > 1)
{
ans++;
cnt[v]--;
}
}
cout << ans << endl;
记录每个点最短路条数,参考 P1144 最短路计数。警察局显然摆放在 $n$ 最优。
memset(d1, -1, sizeof(d1));
memset(d2, -1, sizeof(d2));
d1[1] = 0;
f1[1] = 1;
q.push(1);
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = 0; i < a[u].size(); i++)
{
int e = a[u][i];
if (d1[e] == -1)
{
d1[e] = d1[u] + 1;
f1[e] = f1[u];
q.push(e);
}
else if (d1[e] == d1[u] + 1)
{
f1[e] += f1[u];
}
}
}
d2[n] = 0;
f2[n] = 1;
q.push(n);
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = 0; i < a[u].size(); i++)
{
int e = a[u][i];
if (d2[e] == -1)
{
d2[e] = d2[u] + 1;
f2[e] = f2[u];
q.push(e);
}
else if (d2[e] == d2[u] + 1)
{
f2[e] += f2[u];
}
}
}
ll cnt = f1[n];
for (int i = 2; i < n; i++)
if (d1[i] + d2[i] == d1[n])
cnt = max(cnt, f1[i] * f2[i] * 2);
cout << fixed << setprecision(6) << (double)cnt / f1[n] << endl;
一眼即可得到留下边数一定是 $\min\{k,n-1\}$,考虑留哪些边。
最短路过程中输出松弛的边即可。链式前向星或者 map 记录连接 $u,v$ 的具体是第几条边。
void dijkstra(int u)
{
int v;
long long w;
memset(dis, 0x3f, sizeof(dis));
dis[u] = 0;
q.push({{0, u}, 0});
while (!q.empty())
{
u = q.top().first.second;
w = q.top().first.first;
int id = q.top().second;
q.pop();
if (vis[u])
continue;
vis[u] = 1;
if (id)
cout << id << " ", k--;
if (!k)
return;
for (int i = 0; i < a[u].size(); i++)
{
v = a[u][i].v;
w = a[u][i].w;
if (dis[v] > dis[u] + w)
{
pre[v] = u;
sz[v] = sz[u] + 1;
dis[v] = dis[u] + w;
q.push({{dis[v], v}, mp[u][v]});
}
}
}
}
考虑类似树的直径做法,我们这里找鬼的直径。第一个鬼开始找最远,最远的开始再找最远,正确性证明参考树的直径证明,而且这里边权为一好像更好证吧。
然后对于相隔最远的两个鬼,判断点到鬼的距离是不是 $\leq d$。
cin >> n >> m >> d;
for (int i = 1; i <= m; i++)
cin >> p[i], pos[p[i]] = 1;
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
a[u].push_back(v);
a[v].push_back(u);
}
int x, y, z, ans = 0;
bfs(p[1], 0, x);
bfs(x, 1, y);
bfs(y, 2, z);
for (int i = 1; i <= n; i++)
{
if (dis[i][1] <= d && dis[i][2] <= d)
{
ans++;
}
}
cout << ans << endl;
一眼区间 DP。枚举过渡点 $k$,做完了。
考虑到乘法的正负号带来的影响,我们需要记录每段区间最大值与最小值。
$f_{0,i,j}$ 表示 $\max\limits_{k\in[i,j]}\{a_k\}$,$f_{1,i,j}$ 表示 $\min\limits_{k\in[i,j]}\{a_k\}$。
加法最大最小很好转移,没有正负影响,乘法直接暴力枚举(确信)所有可能算最大最小。
cin >> n;
for (int i = 1; i <= n; i++)
cin >> c[i] >> a[i], c[n + i] = c[i], a[n + i] = a[i];
for (int i = 1; i <= n << 1; i++)
for (int j = 1; j <= n << 1; j++)
f[0][i][j] = -inf, f[1][i][j] = inf;
for (int i = 1; i <= n; i++)
f[0][i][i] = f[0][n + i][n + i] = f[1][i][i] = f[1][n + i][n + i] = a[i];
for (int len = 2; len <= (n << 1); len++)
{
for (int l = 1; l <= (n << 1) - len + 1; l++)
{
int r = l + len - 1;
for (int k = l; k < r; k++)
{
if (c[k + 1] == 't')
{
gmx(f[0][l][r], f[0][l][k] + f[0][k + 1][r]);
gmn(f[1][l][r], f[1][l][k] + f[1][k + 1][r]);
}
else
{
gmx(f[0][l][r], f[0][l][k] * f[0][k + 1][r]);
gmx(f[0][l][r], f[1][l][k] * f[1][k + 1][r]);
gmn(f[1][l][r], f[0][l][k] * f[0][k + 1][r]);
gmn(f[1][l][r], f[0][l][k] * f[1][k + 1][r]);
gmn(f[1][l][r], f[1][l][k] * f[0][k + 1][r]);
gmn(f[1][l][r], f[1][l][k] * f[1][k + 1][r]);
}
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
gmx(ans, f[0][i][i + n - 1]);
cout << ans << endl;
for (int i = 1; i <= n; i++)
if (f[0][i][i + n - 1] == ans)
cout << i << " ";
cout << endl;
此题有很多种做法,可以考虑拆点,将每个点拆成 $(id,company)$ 的形式。也可以建虚点维护连通块,这些是比较常规的做法,码量也比较大。具体可以看我的 AT 提交记录。
这里提供一种炫酷的方法,用数据结构(例 C++ STL set)维护每个点最短路经历了哪些公司,动态处理边权。
void dij(int u)
{
memset(dis, 0x3f, sizeof(dis));
priority_queue<pii> pq;
pq.push({0, u});
dis[u] = 0;
while (!pq.empty())
{
u = pq.top().second;
pq.pop();
for (int i = 0; i < G[u].size(); i++)
{
int v = G[u][i].second;
int w = !S[u].count(G[u][i].first);
if (dis[v] > dis[u] + w)
dis[v] = dis[u] + w, S[v].clear(), S[v].insert(G[u][i].first), pq.push({-dis[v], v});
else if (dis[v] == dis[u] + w)
S[v].insert(G[u][i].first);
}
}
}
简单的状压,也可以维护一个数组记录。每次选一个难得给他找一个女朋友,然后继续搜索,当前已经到最后一个的时候判断是否所有人都有了女朋友。
mod_int 是网上抄的封装好的取模板子,可以忽略不计,算答案时候取模同效。
mod_int<p> dp(int i, int S)
{
if (f[i][S] != -1)
return f[i][S];
if (i == n - 1)
return (S == (1 << n) - 1);
mod_int<p> ret = 0;
for (int j = 0; j < n; j++)
if (a[i + 1][j] && !(S >> j & 1))
ret += dp(i + 1, S | (1 << j));
f[i][S] = ret.val();
return ret.val();
}
mod_int<p> ans;
int main()
{
memset(f, -1, sizeof(f));
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> a[i][j];
for (int j = 0; j < n; j++)
if (a[0][j])
ans += dp(0, 1 << j);
cout << ans << endl;
return 0;
}
显然的,考虑区间 DP。设 $f_{i,j}$ 表示 $i$ 是否能够遇上 $j$,枚举过渡点 $k$,显然有 $f_{i,k}=f_{j,k}=1$ 时可以推出 $f_{i,j}=1$。然后会喜提 WA。注意 $i,j$ 必须有一个人能干掉 $k$ 才可以转移。
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> c, a[i][j] = a[i + n][j] = a[i][j + n] = a[i + n][j + n] = c - '0';
for (int len = 1; len <= n; len++)
{
for (int i = 1; i + len <= n * 2; i++)
{
int j = i + len;
if (len == 1)
{
f[i][j] = 1;
continue;
}
for (int k = i; k <= j; k++)
{
if (f[i][k] && f[k][j] && (a[i][k] || a[j][k]))
{
f[i][j] = 1;
break;
}
}
}
}
vector<int> ans;
for (int i = 1; i <= n; i++)
if (f[i][i + n])
ans.push_back(i);
cout << ans.size() << endl;
for (int i : ans)
cout << i << endl;
考虑两次最短路,一次多源处理两两最短路,一次知道了最短路后暴力加边,在新图上继续最短路即为答案。
cin >> n >> m >> x >> y;
while (m--)
{
cin >> u >> v >> w;
a[u].push_back({v, w});
a[v].push_back({u, w});
}
for (int i = 1; i <= n; i++)
dijkstra(i, dis[i]);
for (int i = 1; i <= n; i++)
a[i].clear();
for (int i = 1; i <= n; i++)
{
long long t, c;
cin >> t >> c;
for (int j = 1; j <= n; j++)
if (dis[i][j] <= t && (i ^ j))
a[i].push_back({j, c});
}
dijkstra(x, dis[0]);
cout << (dis[0][y] ^ inf ? dis[0][y] : -1) << endl;
nxt[i][j]
表示「猫在 $i$ 鼠在 $j$ 猫的下一步」,注意猫同距离找编号最小。
然后是简单的期望,记忆化搜索完成会很方便。转移 $f_{u,v}\gets \sum\limits\frac{f{u^\prime,v^\prime}}{deg_v+1}+\frac{f{u^\prime,v}}{deg_v+1}$,其中 $u^\prime$ 表示 $u$ 的邻居, $v^\prime$ 表示 $v$ 的邻居。
$dis_{u,v}$ 因为边权为 $1$ 可以直接 bfs $n^2$ 预处理出来。
void initNext()
{
memset(nxt, 0x3f, sizeof(nxt));
for (int cat = 1; cat <= n; cat++)
{
for (int cat_neighbour : G[cat])
{
for (int mouse = 1; mouse <= n; mouse++)
{
if (dis[cat][mouse] - 1 == dis[cat_neighbour][mouse])
nxt[cat][mouse] = min(nxt[cat][mouse], cat_neighbour);
}
}
}
}
double dp(int cat, int mouse)
{
if (f[cat][mouse] != -1)
return f[cat][mouse];
if (cat == mouse)
return f[cat][mouse] = 0;
if (dis[cat][mouse] <= 2)
return f[cat][mouse] = 1;
int p = G[mouse].size() + 1;
f[cat][mouse] = 1;
for (int mouse_neighbour : G[mouse])
{
f[cat][mouse] += dp(nxt[nxt[cat][mouse]][mouse], mouse_neighbour) / p;
}
f[cat][mouse] += dp(nxt[nxt[cat][mouse]][mouse], mouse) / p;
// cout << cat << " " << mouse << " " << f[cat][mouse] << endl;
return f[cat][mouse];
}
3 条评论
Orz cyx
cyxakioi
fake