A
由于答案是关于 的多项式。于是暴力搜索出
的解然后拉格朗日插值。具体地:
$$
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int P = 998244353;
ll n, m, k, inv[5];
ll C(ll x, int t) {
ll res = 1;
for (int i = 0; i != t; i++) res = 1ll * res * (x - i) % P * inv[i + 1] % P;
return res;
}
int main() {
scanf("%lld%lld%lld", &n, &m, &k);
n--;
m--;
inv[0] = 0;
inv[1] = 1;
inv[2] = (P + 1) / 2;
inv[3] = (P + 1) / 3;
inv[4] = 1ll * inv[2] * inv[2] % P;
if (k == 1)
puts("1");
if (k == 2)
printf("%lld", (n + m) % P);
if (k == 3)
printf("%lld", (m * n * 4 + C(m, 2) + C(n, 2)) % P);
if (k == 4)
printf("%lld", (n * m + (n * C(m, 2) + m * C(n, 2)) % P * 11 + C(m, 3) + C(n, 3)) % P);
if (k == 5)
printf("%lld", (C(m, 2) * C(n, 2) % P * 64 + (n * C(m, 3) + m * C(n, 3)) % P * 26 +
(n * C(m, 2) + m * C(n, 2)) % P * 7 + C(m, 4) + C(n, 4)) %
P);
return 0;
}B
若没有对称轴,答案为 .
若有一条对称轴,凸包上所有点作一条到对称轴的垂线,发现体积是多个圆台体积相加。
若有多个对称轴,那么一定交于同一点。最终扫过的体积就是以到这个点距离最远的点为半径的球。
问题转化为求对称轴。我们找到这个凸多边形的几何中心 。若这个多边形存在对称轴,那么几何中心一定在这条对称轴上。用点到几何中心的长度代表点,用边端点的两个点到几何中心的夹角来代表边。把得到的数组复制一遍再接上去。那么如果存在一个长为
的回文串,即存在一个对称轴。
#include <bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
const int MAXN = 1e5 + 10;
const ld EPS = 1e-6;
const ld Pi = acos(-1);
int n, Hs, L1, St, Cnt, Len[MAXN * 4];
ll H[MAXN * 4];
ld Maxr;
struct Point {
ld X, Y;
Point() { X = Y = 0; }
Point(ld Nx, ld Ny) { X = Nx, Y = Ny; }
ld Len2() { return X * X + Y * Y; }
ld Dot(Point S) { return X * S.X + Y * S.Y; }
ld Len1() { return sqrt(X * X + Y * Y); }
Point Num(ld S) { return Point(X * S, Y * S); }
Point operator+(Point S) { return Point(X + S.X, Y + S.Y); }
Point operator-(Point S) { return Point(X - S.X, Y - S.Y); }
} Dot[MAXN * 4], Ht;
ld Min(ld A, ld B) { return A < B ? A : B; }
ld Max(ld A, ld B) { return A > B ? A : B; }
ld Calc() {
ld Lr, Nr, Dt, Ret = 0;
int Sl, Sr;
Point Last, Now;
if (St & 1)
Now = Dot[St / 2 + 1], Lr = 0, Sl = Sr = St / 2 + 1, Nr = 0;
else
Sl = St / 2, Sr = St / 2 + 1, Now = (Dot[Sl] + Dot[Sr]).Num(0.5), Nr = (Dot[Sl] - Dot[Sr]).Len1() / 2;
// printf("St=%d\n",St);
for (Sl--, Sr++; Sr - Sl + 1 <= n; Sl--, Sr++)
Last = Now, Lr = Nr, Now = (Dot[Sl] + Dot[Sr]).Num(0.5), Nr = (Dot[Sr] - Dot[Sl]).Len1() / 2,
Dt = (Now - Last).Len1(), Ret += Pi * (Lr * Lr + Nr * Nr + Lr * Nr) * Dt / 3;
return Ret;
}
void Symmetry() {
for (int i = 1; i <= n; i++)
H[++Hs] = (Dot[i] - Dot[i - 1]).Dot(Dot[i + 1] - Dot[i]), H[++Hs] = (Dot[i + 1] - Dot[i]).Len2();
for (int i = 1; i <= Hs; i++) H[i + Hs] = H[i];
H[0] = -5233, H[4 * n + 1] = -233, Hs <<= 1;
for (int i = 1, Nr = 0, Mid = 1; i <= Hs; i++) {
Len[i] = Min(Nr - i + 1, Len[2 * Mid - i]);
while (Len[i] + i > Nr && i - Len[i] >= 0 && H[i + Len[i]] == H[i - Len[i]]) Len[i]++, Nr++;
i + Len[i] > Nr ? Mid = i : 0;
}
for (int i = 1; i <= n; i++) Ht = Ht + Dot[i];
Ht = Ht.Num(1.0 / n);
for (int i = 1; i <= n; i++) Maxr = Max(Maxr, (Dot[i] - Ht).Len1());
for (int i = n + 1; i <= n * 3; i++)
if (Len[i] >= n)
St = i, Cnt++;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%Lf%Lf", &Dot[i].X, &Dot[i].Y), Dot[i + n] = Dot[i];
Dot[0] = Dot[n], Symmetry(), Cnt /= 2;
if (Cnt == 0)
return puts("0"), 0;
if (Cnt >= 2)
return printf("%.10Lf\n", 4 / 3.0 * Pi * Maxr * Maxr * Maxr), 0;
printf("%.10Lf\n", Calc());
}C
如果 相同就无解。考虑构造答案,没有出现过的数字随便放。剩下的数字每种最多出现一个,错排即可。
#include <bits/stdc++.h>
using namespace std;
int a[100005], b[100005];
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = i;
}
bool flag = 1;
for (int i = 1; i <= n; i++) {
if (a[i] == b[i]) {
flag = 0;
for (int j = 1; j <= n; j++) {
if (i != j && a[i] != b[j] && a[j] != b[i]) {
swap(b[i], b[j]);
flag = 1;
}
}
if (flag == 0)
break;
}
}
if (flag) {
cout << "YES\n";
for (int i = 1; i <= n; i++) {
cout << b[i] << " ";
}
cout << endl;
} else
cout << "NO\n";
}
}E
对于下凸序列,若一对数 ,分类讨论
在左边还是右边看逆序对情况,发现左左和右左会贡献
,也就是只和
放在哪里有关。
若 ,发现右左和右右会贡献
,也就是只和
放在哪里有关。
综上,对于某一个数 ,若将其放在左边,贡献为
的个数;若将其放在右侧, 贡献为
的个数。我们不妨分别将数值设为
和
。
随着数列的增加, 是不会变的,
会越变越大。当
的时候,我们就会把这个数放在左边,否则放在右边。
于是用线段树维护每个数 的值。当这个值小于等于
的时候我们就把它放在左边去。同时我们需要维护每个数的
,也用线段树维护。
对于上凸序列,我们只需要把所有数取负然后离散化做一遍下凸的,两个答案取 。时间复杂度
。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int i, j, k, n, m, t, a[200500], p[200500], g[200500], f[200500];
ll res[200500];
void add(int x, int y) {
for (; x <= 200005; x += (-x & x)) {
f[x] += y;
}
}
int get(int x, int y = 0) {
for (; x; x -= (-x & x)) {
y += f[x];
}
return y;
}
void fuck() {
vector<int> v[n + 5];
memset(f, 0, sizeof(f));
for (i = 1; i <= n; i++) {
g[i] = get(a[i]);
add(a[i], 1);
p[a[i]] = i;
}
memset(f, 0, sizeof(f));
for (i = 1; i <= n; i++) {
int l = p[i], r = n, md, ans = p[i];
while (l <= r) {
md = (l + r) / 2;
if (get(md) > 2 * g[p[i]])
r = md - 1;
else
ans = max(ans, md), l = md + 1;
}
v[ans].push_back(i);
add(p[i], 1);
}
memset(f, 0, sizeof(f));
ll cur = 0;
for (i = 1; i <= n; i++) {
cur += get(n) - get(a[i]);
for (auto j : v[i]) {
add(j, -1);
}
add(a[i], 1);
res[i] = min(res[i], cur);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (i = 1; i <= n; i++) {
cin >> a[i];
p[i] = a[i];
res[i] = 1e18;
}
sort(p + 1, p + n + 1);
for (i = 1; i <= n; i++) {
a[i] = lower_bound(p + 1, p + n + 1, a[i]) - p;
}
fuck();
for (i = 1; i <= n; i++) a[i] = n + 1 - a[i];
fuck();
for (i = 1; i <= n; i++) cout << res[i] << '\n';
}F
所有能消除的组合是形如 。那么不妨对于
的数
,改成
。这样的话两个数能消除当且仅当他们相同。于是可以直接用栈模拟一边。
#include <stdio.h>
int main() {
int n, x, l, r, i = 0, s = 0, a[100002];
for (scanf("%d%d", &n, &x); i < n; ++i) {
scanf("%d", &a[i]);
if (i && (a[i] == a[i - 1] || a[i] + a[i - 1] == x))
++s, i -= 2, n -= 2;
}
for (i = 0; i * 2 + 1 < n && (a[i] == a[n - 1 - i] || a[i] + a[n - 1 - i] == x); ++i) ++s;
printf("%d", s);
}G
首先发现,.* 能匹配所有字符串。所以肯定有最小长度 。
对于 ,长度为
,方案数为
。
否则枚举所有可能的长度为 的表达式,然后判断是否能匹配即可。
#include <bits/stdc++.h>
using namespace std;
int i, n, m, ans, a;
char s[2000001];
int main() {
int ti;
cin >> ti;
while (ti--) {
cin >> s;
n = strlen(s);
if (n == 1) {
ans = 1;
a = 2;
} else {
ans = 2;
a = 2;
if (n == 2)
a += 4;
int pd = 1;
for (int i = 1; i < n; i++)
if (s[i] != s[i - 1]) {
pd = 0;
break;
}
if (pd)
a += 2;
}
printf("%d %d\n", ans, a);
}
}H
发现定义和双极定向一样的。(不知道的可以看牛客第三场的 )
定向了后,通过拓扑序求出每个点的目标点值,然后随便求个以 为根的
生成树化成树上问题。
首先考虑链的方案是什么。应该是每次选择 所在点进行操作,从而做到排序。
那么对于一颗普通的树,取出所有叶子向上直到分叉点的链,解决这一部分后删掉递归处理。因为每次叶子数量至少 ,所以做到
次。具体实现可以参考代码。
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define Fast_IO ios::sync_with_stdio(false);
#define DEBUG fprintf(stderr, "Running on Line %d in Function %s\n", __LINE__, __FUNCTION__)
#define fir first
#define sec second
#define mod 998244353
#define ll long long
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
inline int read() {
char ch = getchar();
int nega = 1;
while (!isdigit(ch)) {
if (ch == '-')
nega = -1;
ch = getchar();
}
int ans = 0;
while (isdigit(ch)) {
ans = ans * 10 + ch - 48;
ch = getchar();
}
if (nega == -1)
return -ans;
return ans;
}
typedef pair<int, int> pii;
void print(vector<int> x) {
for (int i = 0; i < (int)x.size(); i++) printf("%d%c", x[i], " \n"[i == (int)x.size() - 1]);
}
#define N 2005
int fa[N], id[N], val[N], a[N];
int n, m, S, T;
int u[N], v[N], ok[N];
vector<int> G[N], H[N];
int dgr[N], rk[N], rev[N];
int toposort() {
for (int i = 1; i <= n; i++)
if (ok[i]) {
for (int j : G[i]) dgr[j]++;
}
queue<int> q;
q.push(S);
int cnt = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
rk[u] = cnt++;
for (int v : G[u]) {
if (!--dgr[v])
q.push(v);
}
}
return cnt == n;
}
vector<int> ans, cur;
int vis[N];
void dfs(int u) {
if (!ans.empty())
return;
cur.pb(u);
vis[u] = 1;
for (int v : H[u]) {
if (vis[v])
continue;
if (ok[v]) {
ans = cur;
ans.pb(v);
return;
} else
dfs(v);
if (!ans.empty())
return;
}
cur.pop_back();
}
namespace APC001H {
int p[N];
vector<int> ans, e[N];
void jump(int x) {
if (x)
jump(fa[x]), a[fa[x]] = a[x], p[a[x]] = fa[x];
}
void shift(int x) {
int t = a[0];
jump(x);
ans.push_back(p[a[x] = t] = x);
}
int solve(int x, int v) {
int res = n;
for (int y : e[x]) {
int t = solve(y, v);
if (a[t] > a[res])
res = t;
}
return res < n ? res : a[x] > v ? x : -1;
}
void Main() {
e[fa[0] = n].push_back(0);
for (int i = 1; i < n; i++) e[fa[i]].push_back(i);
for (int i = 0; i < n; i++) p[a[i]] = i;
for (int i = n; i--; shift(i), e[fa[i]].pop_back())
while (a[0] != i) shift(solve(p[i], a[0]));
assert(ans.size() <= 10000);
cout << ans.size() << "\n";
for (int id : ans) {
vector<int> rv;
int cur = id;
while (cur) {
rv.pb(cur);
cur = fa[cur];
}
rv.pb(0);
reverse(rv.begin(), rv.end());
printf("%d ", (int)rv.size());
for (int j : rv) printf("%d ", rev[j]);
cout << "\n";
}
}
};
int edg[1005][1005];
signed main() {
cin >> n >> m >> S >> T;
for (int i = 1; i <= n; i++) val[i] = read();
for (int i = 1; i <= m; i++) {
u[i] = read(), v[i] = read();
edg[u[i]][v[i]] = edg[v[i]][u[i]] = 1;
H[u[i]].pb(v[i]), H[v[i]].pb(u[i]);
}
G[S].pb(T);
ok[S] = ok[T] = 1;
while (!toposort()) {
int fl = 0;
for (int i = 1; i <= n && !fl; i++)
if (ok[i]) {
for (int j : H[i])
if (!ok[j]) {
ans.clear();
memset(vis, 0, sizeof(vis));
vis[i] = 1;
cur = { i };
// printf("* %d %d\n",i,j);
dfs(j);
if (ans.empty())
continue;
if (rk[ans[0]] > rk[ans.back()])
reverse(ans.begin(), ans.end());
// print(ans);
for (int k = 0; k + 1 < (int)ans.size(); k++)
G[ans[k]].pb(ans[k + 1]), ok[ans[k]] = 1;
fl = 1;
break;
}
}
if (!fl)
cout << "-1\n", exit(0);
}
for (int i = 1; i <= n; i++) rev[rk[i]] = i;
for (int i = 1; i <= n; i++)
for (int v : G[i])
if (edg[i][v])
fa[rk[v]] = rk[i];
for (int i = 1; i <= n; i++) a[rk[i]] = val[i] - 1;
APC001H::Main();
return 0;
}J
如果数组 区间
和能被
整除,那前缀和数组
一定满足
。
那么考虑 表示
的个数。方案数即为
。
表示放了
,总共放了
个,优异度为
的方案数。直接
计算。
$$
#include <cstdio>
using namespace std;
const int M = 998244353;
int n, K, t;
long f[65][2100];
long C[70][70];
int main() {
scanf("%d%d%d", &n, &K, &t);
C[0][0] = 1;
for (int i = 1; i <= 65; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % M;
}
f[0][0] = 1;
for (int u, i = 0; i < K; i++)
for (int k = n; k; k--)
for (int l = t; l >= 0; l--) {
for (int u, j = 1; j <= k; j++) {
u = j * (i == 0 ? (j + 1) : (j - 1)) / 2;
if (u > l)
break;
f[k][l] += f[k - j][l - u] * C[k][j] % M;
}
f[k][l] %= M;
}
printf("%ld\n", f[n][t]);
}K
若堆数为 ,先手必胜。
若堆数为 ,谁取完了一堆谁就输了。所以每一堆有一个石子不能取。我们令
,就变成了
问题,谁最后操作不了就赢了。也就是
则先手必败,否则先手必胜。
若堆数为 ,先手操作最大的那一堆石头,从而变成堆数为
且异或和为
的局面使其胜利。
若堆数为 ,谁让堆数
谁就输了,所以还是看
是否为
。
所以堆数为奇数先手必胜。否则令 ,看多少区间异或和为
。
那么只需要前缀异或一下,离线下来莫队求解。时间复杂度 。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 1, B = 300, T = 2e6 + 1;
int n, q, s[N], bl[N], L, R, a[2][T];
ll sum, ans[N];
struct que {
int l, r, id;
} t[N];
int operator<(const que& a, const que& b) {
return bl[a.l] ^ bl[b.l] ? a.l < b.l : (bl[a.l] & 1 ? a.r < b.r : a.r > b.r);
}
int main() {
scanf("%d%d", &n, &q);
for (int i = 0; i <= n; i++) bl[i] = i / B + 1;
for (int i = 1; i <= n; i++) scanf("%d", s + i), s[i] = (s[i] - 1) ^ s[i - 1];
for (int i = 1; i <= q; i++) {
scanf("%d%d", &t[i].l, &t[i].r);
t[i].l--;
t[i].id = i;
}
sort(t + 1, t + 1 + q);
a[0][0] = 1;
for (int i = 1; i <= q; i++) {
while (R < t[i].r) ++R, sum += a[R & 1][s[R]]++;
while (L > t[i].l) --L, sum += a[L & 1][s[L]]++;
while (R > t[i].r) sum -= --a[R & 1][s[R]], --R;
while (L < t[i].l) sum -= --a[L & 1][s[L]], ++L;
ans[t[i].id] = 1ll * (R - L + 1) * (R - L) / 2 - sum;
}
for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]);
return 0;
}L
在一个边双里,最小值和最大值可以出现在同一个环里。因为我们在最小边和最大边中间加上一个点,这图还是一个边双。
做法可以建图,以这新建的两个点跑网络流,然后把有流量的边拿出来跑欧拉回路即可。
因为网络流除了源汇点,每个点的输入流量等于输出流量。而由于是边双,所以最大流至少为 ,也就是源汇点的度数也会是
,那么满足欧拉回路条件。
#include <bits/stdc++.h>
#define ri int
#define ll long long
#define fi first
#define se second
using namespace std;
const int Maxn = 1e6;
vector<int> s[Maxn + 5];
vector<pair<int, int>> adj[Maxn + 5];
int vis[Maxn + 5], mark[Maxn + 5];
int e[Maxn + 5];
int dfn[Maxn + 5], low[Maxn + 5], bridge[Maxn + 5], id, vt;
int lt[Maxn + 5], nt[Maxn + 5], ed[Maxn + 5], val[Maxn + 5], have[Maxn + 5], cnt = 1;
int n, m;
inline bool cmp(int x, int y) { return e[x] < e[y]; }
inline void addedge(int x, int y, int z) {
ed[++cnt] = y;
nt[cnt] = lt[x];
lt[x] = cnt;
val[cnt] = z;
}
inline void add(int x, int y, int z) {
addedge(x, y, z);
addedge(y, x, 0);
}
void tarjan(int u, int fa) {
dfn[u] = low[u] = ++vt;
for (auto e : adj[u]) {
int v = e.fi;
if (v == fa)
continue;
if (!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) {
bridge[e.se] = 1;
}
} else
low[u] = min(low[u], dfn[v]);
}
}
void dfs(int u) {
vis[u] = 1;
for (auto e : adj[u])
if (!bridge[e.se]) {
s[id].push_back(e.se);
if (!vis[e.fi])
dfs(e.fi);
}
}
int maxflow(int S, int T, int tot) {
int ans = 0;
while (1) {
vector<int> pre(tot + 1), to(tot + 1);
queue<int> q;
pre[S] = S, q.push(S);
while (!q.empty()) {
int u = q.front();
q.pop();
for (ri i = lt[u]; i; i = nt[i]) {
int v = ed[i];
if (!pre[v] && val[i]) {
pre[v] = u, to[v] = i;
q.push(v);
}
}
}
if (!pre[T])
return ans;
int u = T, flow = 1e9;
while (u != S) {
int e = to[u];
u = pre[u];
// printf("Left: %d ",u);
flow = min(flow, val[e]);
}
u = T;
while (u != S) {
int e = to[u];
u = pre[u];
// printf("Left: %d ",u);
val[e] -= flow, val[e ^ 1] += flow;
have[e] += flow, have[e ^ 1] -= flow;
// printf("%d %d\n",u,val[e]);
}
ans += flow;
}
}
void buildcircle(int u, vector<int>& ans) {
ans.push_back(u);
for (ri i = lt[u]; i; i = nt[i]) {
int v = ed[i];
if (have[i] > 0) {
--have[i];
buildcircle(v, ans);
break;
}
}
}
int main() {
scanf("%d%d", &n, &m);
vector<pair<int, int>> edge(m + 1);
for (ri i = 1; i <= m; i++) {
int x, y;
scanf("%d%d%d", &x, &y, &e[i]);
adj[x].push_back(make_pair(y, i));
adj[y].push_back(make_pair(x, i));
edge[i] = make_pair(x, y);
}
tarjan(1, 0);
for (ri i = 1; i <= n; i++)
if (!vis[i])
++id, dfs(i);
int mxd = -1, ch = 0;
for (ri i = 1; i <= id; i++) {
sort(s[i].begin(), s[i].end());
s[i].erase(unique(s[i].begin(), s[i].end()), s[i].end());
sort(s[i].begin(), s[i].end(), cmp);
if ((int)s[i].size() >= 2) {
int d = e[s[i].back()] - e[*s[i].begin()];
if (d > mxd) {
mxd = d, ch = i;
}
}
}
int S = n + 1, T = n + 2, mn = *s[ch].begin(), mx = s[ch].back();
add(S, edge[mn].fi, 1);
add(S, edge[mn].se, 1);
add(edge[mx].fi, T, 1);
add(edge[mx].se, T, 1);
for (ri x : s[ch])
if (x != mn && x != mx) {
addedge(edge[x].fi, edge[x].se, 1);
addedge(edge[x].se, edge[x].fi, 1);
}
maxflow(S, T, n + 2);
printf("%d\n", mxd);
vector<int> ans1, ans2;
buildcircle(S, ans1);
buildcircle(S, ans2);
printf("%d\n", (int)ans1.size() + (int)ans2.size() - 4);
for (ri u : ans1)
if (u != S && u != T)
printf("%d ", u);
reverse(ans2.begin(), ans2.end());
for (ri u : ans2)
if (u != S && u != T)
printf("%d ", u);
puts("");
return 0;
}

全部评论
(1) 回帖