B题
前缀和+计算贡献
对于a[1]:
a[1]仅在l=1时出现,
r=1,a[1]*b[1]; r=2,a[1]*(b[1]+b[2]),…,r=n,a[1]*(b[1]+b[2]+…+b[n]),因此b[1]贡献了n次,b[2]贡献了n-1次,…,b[n]贡献了1次
对于a[2]:
a[2]仅在l=1和l=2时出现
l=1时,对于a[2]有b[2]贡献n-1次,…,b[n]贡献1次
l=2时,对于a[2]有b[2]贡献n-1次,…,b[n]贡献1次
以此类推得出:
a[i]对答案的贡献为i*a[i]*(b[i]*(n-i+1)+b[i+1]*(n-i)+…+b[n]*1)
代码如下:
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
const ll mod = 998244353;
ll n, a[maxn], b[maxn];
int main(void) {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
a[i] = (a[i] * i) % mod;
}
for (int i = 1; i <= n; i++) {
scanf("%lld", &b[i]);
b[i] = (b[i] * (n - i + 1)) % mod;
b[i] = (b[i] + b[i - 1]) % mod;
}
ll ans = 0;
for (int i = 1; i <= n; i++)
ans = (ans + a[i] * (b[n] - b[i - 1]) % mod) % mod;
printf("%lld\n", ans);
return 0;
}
C题
对于最后一个式子,要用到矩阵快速幂(求斐波那契数)+逆元(除法)
根据欧拉定理,2^x同余2^(x%p)(mod p)
参考:https://zhuanlan.zhihu.com/p/35060143
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll p = 998244353;
const ll p_1 = p - 1;
ll x1, x2, tmp[101];
struct Maxtrix {
ll m[2][2];
Maxtrix() {
memset(m, 0, sizeof(m));
}
};
ll gcd(ll a, ll b) {
while (b) {
ll t = b;
b = a % b;
a = t;
}
return a;
}
ll ksm(ll a, ll n) {
ll ret = 1;
while (n) {
if (n & 1)ret = (ret * a) % p;
a = (a * a) % p;
n >>= 1;
}
return ret % p;
}
Maxtrix Multi(Maxtrix a, Maxtrix b) {
Maxtrix res;
res.m[0][0] = (a.m[0][0] * b.m[0][0] + a.m[0][1] * b.m[1][0]) % p_1;
res.m[0][1] = (a.m[0][0] * b.m[0][1] + a.m[0][1] * b.m[1][1]) % p_1;
res.m[1][0] = (a.m[1][0] * b.m[0][0] + a.m[1][1] * b.m[1][0]) % p_1;
res.m[1][1] = (a.m[1][0] * b.m[0][1] + a.m[1][1] * b.m[1][1]) % p_1;
return res;
}
void fastm(ll &x,ll n) {
Maxtrix res, a;
res.m[0][0] = res.m[1][1] = 1;
a.m[0][0] = a.m[0][1] = a.m[1][0] = 1;
while (n) {
if (n & 1)
res = Multi(res, a);
a = Multi(a, a);
n >>= 1;
}
x = res.m[0][0] % (p - 1);
}
int main(void) {
scanf("%lld%lld", &x1, &x2);
if (x1 > x2)swap(x1, x2);
ll pow = gcd(x1, x2);
ll fx1, fx2, fx3, f0 = 0, f1 = 1;
fastm(fx1, x1 - 1); fastm(fx2, x2 - 1); fastm(fx3, pow - 1);
ll a = ((ksm(2, fx1) + p) - 1) % p;
ll b = ((ksm(2, fx2) + p) - 1) % p;
ll c = ((ksm(2, fx3) + p) - 1) % p;
c = ksm(c, p - 2);
printf("%lld\n", (a * b) % p * c % p);
return 0;
}
D题
代码如下:
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
int n, q, a[maxn];
ll c[maxn], t[maxn];
struct Shu {
int L, R;
ll Sum;
Shu() :L(0), R(0), Sum(0) {}
}tree[maxn << 2];
void update(int x) {
tree[x].Sum = tree[x << 1].Sum + tree[(x << 1) | 1].Sum;
}
void Build_Tree(int node, int L, int R) {
tree[node].L = L;
tree[node].R = R;
if (L == R) {
tree[node].Sum = t[L];
return;
}
int mid = (L + R) / 2;
Build_Tree(node * 2, L, mid);
Build_Tree(node * 2 + 1, mid + 1, R);
update(node);
}
ll Search(int node, int L, int R) {
if (tree[node].L >= L && tree[node].R <= R)//全部在区间之内
return tree[node].Sum;
if (tree[node].R < L || tree[node].L > R)//完全在区间之外
return 0;
ll ret = 0;
if (tree[node << 1].R >= L)ret += Search(node << 1, L, R);//左子树的右边在区间之内
if (tree[(node << 1) | 1].L <= R)ret += Search((node << 1) | 1, L, R);//右子树的左边在区间之内
return ret;
}
void Change(int node, int pos, ll data) {
if (tree[node].L == tree[node].R) {
tree[node].Sum = data;
return;
}
if (pos <= tree[node << 1].R)Change(node << 1, pos, data);//在左子树中
else Change((node << 1) | 1, pos, data);//在右子树中
update(node);
}
int main(void) {
scanf("%d %d", &n, &q);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) {
scanf("%lld", &c[i]);
t[i] = 1LL * a[i] * c[i];
}
Build_Tree(1, 1, n);
while (q--) {
int cmd, L, R;
scanf("%d %d %d", &cmd, &L, &R);
if (cmd == 1)
printf("%lld\n", 4LL * Search(1, L, R));
else {
c[L] += R;
Change(1, L, t[L] = 1LL * a[L] * c[L]);
}
}
return 0;
}
E题
题解:以零点作为超级起点,连接每个初始点燃的结点,计算出零点到每个点的距离,即每个结点的点燃时间,然后遍历每个结点,去找它们的相邻结点,根据距离差和边权来计算可能需要的最大时间。
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 5;
int n, m, burned[maxn], vis[maxn];
ll dis[maxn];
struct edge {
int u, v, w;
};
vector<edge>e[maxn];
struct node {
int id;
ll dis;
bool operator <(node a) const{
return dis > a.dis;
}
};
bool cmp(int a, int b) {
return dis[a] < dis[b];
}
int main(void) {
memset(dis, inf, sizeof(dis));
scanf("%d %d", &n, &m);
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
e[u].push_back(edge{ u,v,w });
e[v].push_back(edge{ v,u,w });
}
dis[0] = 0;
priority_queue<node>pq;
for (int i = 0; i < m; i++) {
int x; scanf("%d", &x);
dis[x] = 0;
pq.push(node{ x,0 });
}
while (!pq.empty()) {
node head = pq.top(); pq.pop();
int u = head.id;
if (burned[u])continue;
burned[u] = 1;
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i].v;
if (dis[v] > e[u][i].w + dis[u]) {
dis[v] = e[u][i].w + dis[u];
pq.push(node{ v,dis[v] });
}
}
}
ll ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < e[i].size(); j++) {
int v = e[i][j].v;
if (vis[v])continue;
ans = max(ans, max(2 * max(dis[i], dis[v]), e[i][j].w + dis[i] + dis[v]));
}
vis[i] = 1;
}
printf("%lld\n", ans);
return 0;
}
F题
Dinic算法+拆点
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 5005;//拆点:200 无向边变有向边 1000*2 2000+200反向弧,总共4400
const int inf = 0x3f3f3f3f;
int n, m, num;
int depth[maxn], cur[maxn], start = 1, tend;
int head[maxn], cnt;
struct edge{
int to, cap, next;
} e[maxn];
void addedge(int u, int v, int w){
e[cnt].to = v;
e[cnt].cap = w;
e[cnt].next = head[u];
head[u] = cnt++;//记录u指向的最后一条边
}
bool bfs(){
memset(depth, -1, sizeof(depth));
int Q[maxn];
int Thead = 0, Ttail = 0;
Q[Ttail++] = start;;
depth[start] = 0;
while (Thead < Ttail){
int u = Q[Thead];
if (u == tend)
return true;
for (int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if (depth[v] == -1 && e[i].cap > 0){
depth[v] = depth[u] + 1;
Q[Ttail++] = v;
}
}
Thead++;
}
return false;//汇点是否成功标号,也就是说是否找到增广路
}
int dfs(int u, int cap){
if (u == tend) return cap;
int flow = 0, f;
for (int &i = cur[u]; i != -1; i = e[i].next){
int v = e[i].to;
if (depth[v] == depth[u] + 1 && e[i].cap){
f = dfs(v, min(cap - flow, e[i].cap));
e[i].cap -= f;
e[i ^ 1].cap += f;
flow += f;
if (flow == cap)
break;
}
}
if (!flow)
depth[u] = -2;//防止重搜
return flow;
}
int maxflow(){
int flow = 0, f, limit = n << 1;
while (bfs()){
for (int i = 1; i <= limit; i++)
cur[i] = head[i];
while (f = dfs(start, inf))
flow += f;
}
return flow;
}
int main(void){
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
int w; scanf("%d", &w);
addedge(i, i + n, w);//将i拆成 i为入点,i+n为出点
addedge(i + n, i, 0);//反向弧
}
for (int i = 1; i <= m; i++) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
addedge(u + n, v, w);//u的出点和v的入点连接
addedge(v, u + n, 0);//<u,v>反向弧
addedge(u, v + n, 0);//<v,u>反向弧
addedge(v + n, u, w);//v的出点和u的入点连接
}
scanf("%d%d", &tend, &num);
tend += n;//要经过tend->tend+n,所以得把tend+n看作汇点
int ans = maxflow();
printf("%.8f\n", 1.0 * num / ans);
return 0;
}
G题
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn = 16;
int n, x[maxn], y[maxn], dp[1 << maxn], IsOneEdge[1 << maxn], limit;
vector<int>edge;
int dfs(int subset) {
for (int i = 0; i < edge.size(); i++) {
if (!((edge[i] & subset) ^ edge[i])) {
int left = subset ^ edge[i];
if (dp[left] == -1)dp[left] = dfs(left);
if (!dp[left])return 1;
}
}
return 0;
}
int main(void) {
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d %d", &x[i], &y[i]);
limit = 1 << n;
dp[0] = 0;
for (int i = 1; i < limit; i++) {
dp[i] = -1;
int hav[maxn], len = 0;
bool flag = true;
for (int j = 0; j < n; j++)
if (i & (1 << j))
hav[len++] = j;
if (len >= 2) {
int dy = y[hav[0]] - y[hav[1]], dx = x[hav[0]] - x[hav[1]];
for (int k = 2; k < len; k++) {
int dy2 = y[hav[0]] - y[hav[k]], dx2 = x[hav[0]] - x[hav[k]];
if (dy * dx2 != dy2 * dx) {
flag = false;
break;
}
}
}
if (flag) {
IsOneEdge[i] = 1;
edge.push_back(i);
dp[i] = 1;
}
}
printf("%s\n", dfs((1 << n) - 1) == 1 ? "zyh" : "fzj");
return 0;
}