C I love playing games
题意:给定一张无向图,二人轮流从出发点 x , y x,y x,y 开始移动,二人不可走到同一位置。给定终点 z z z,先走到 z z z 的获胜,否则平局。问最有情况下的游戏结果。
解法:首先跑最短路,如果到终点的最短路长度都不相同一定有胜负之别。否则,就需要使用类似于 SG 函数去做 DP 了。
首先建立最短路图,构造 f [ x ] [ y ] [ 0 / 1 ] f[x][y][0/1] f[x][y][0/1] 表示当前二人点在 x , y x,y x,y,轮到先手/后手走的时候的结局,可以考虑使用记忆化搜索的方法。注意要特判当二人同时走到时的情形,这种情况下应算平局。
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
struct node
{
int x;
int y;
int k;
};
vector<node> status;
int dis[5005];
vector<int> graph[5005], vi[5005];
void topo(int place)
{
queue<int> q;
q.push(place);
dis[place] = 1;
while(!q.empty())
{
int tp = q.front();
q.pop();
for (auto i : graph[tp])
if(dis[i]==inf)
{
dis[i] = dis[tp] + 1;
q.push(i);
}
}
return;
}
int f[5005][5005][2];
int dfs(int x,int y,int z,bool k)
{
if(k==0 && (x==z || y==z))//一定让先手来操作时判定游戏结果——因为平局的条件较为苛刻
{
if(x==z && y==z)//先判定同时到达
return 2;
if(x==z)
return 1;
else
return 3;
}
if(f[x][y][k])
return f[x][y][k];
if(k==0)
{
int ans = 3;
for (auto i : vi[x])
if(i!=y || i==z)
ans = min(ans, 4 - dfs(i, y, z, k ^ 1));
f[x][y][k] = ans;
}
else
{
int ans = 3;
for (auto i : vi[y])
if(i!=x || i==z)
ans = min(ans, 4 - dfs(x, i, z, k ^ 1));
f[x][y][k] = ans;
}
status.push_back((node){x, y, k});
return f[x][y][k];
}
int main()
{
int n, m, t;
scanf("%d", &t);
while(t--)
{
int u, v, x, y, z;
scanf("%d%d", &n, &m);
status.clear();
for (int i = 1; i <= n;i++)
{
graph[i].clear();
vi[i].clear();
dis[i] = inf;
}
scanf("%d%d%d", &x, &y, &z);
for (int i = 1; i <= m;i++)
{
scanf("%d%d", &u, &v);
graph[u].push_back(v);
graph[v].push_back(u);
}
topo(z);
if(dis[x]!=dis[y])
{
if(dis[x]>dis[y])
printf("3\n");
else
printf("1\n");
continue;
}
if(dis[x]==inf)
{
printf("2\n");
continue;
}
for (int i = 1; i <= n;i++)
for (auto j : graph[i])
if(dis[i]==dis[j]+1)
vi[i].push_back(j);
printf("%d\n", dfs(x, y, z, 0));
for (auto i : status)
f[i.x][i.y][i.k] = 0;
}
return 0;
}
G I love data structure
题意:给定一个序列 ( a i , b i ) (a_i,b_i) (ai,bi),可能执行以下四种操作:
- 求出 ∑ i = l r a i b i \displaystyle \sum_{i=l}^{r} a_ib_i i=l∑raibi。
- 给定参数 p p p,若 p p p 为 0 0 0 则将区间 [ l , r ] [l,r] [l,r] 内的所有 a a a 值增加 x x x,否则增加 b b b 的值。
- 将区间 [ l , r ] [l,r] [l,r] 中所有的 ( a , b ) (a,b) (a,b) 变成 ( 3 a + 2 b , 3 a − 2 b ) (3a+2b,3a-2b) (3a+2b,3a−2b)。
- 将区间 [ l , r ] [l,r] [l,r] 中 a , b a,b a,b 值对换。
解法:容易发现,第三个操作的本质是对矩阵 [ a b ] \begin{bmatrix} a & b \end{bmatrix} [ab] 乘以了一个变换矩阵 [ 3 3 2 − 2 ] \begin{bmatrix} 3 & 3 \\ 2 & -2 \\ \end{bmatrix} [323−2];而第四个操作是乘以了 [ 0 1 1 0 ] \begin{bmatrix} 0 & 1 \\ 1 & 0 \\ \end{bmatrix} [0110]。
对于第二个操作,则是直接对 a a a 或者 b b b 进行加和。
对于区间操作,可以考虑使用线段树。在线段树上,我们可以维护以下一些元素: ∑ a 2 , ∑ b 2 , ∑ a , ∑ b , ∑ a b \sum a^2,\sum b^2,\sum a,\sum b,\sum ab ∑a2,∑b2,∑a,∑b,∑ab。由于此处仅有二次,因而这些元素之间可以互相转化。
首先是考虑第二个操作,仅以 a a a 在长度为 l l l 的区间上增加 x x x 举例: ∑ a = ∑ a + x l \sum a=\sum a+xl ∑a=∑a+xl, ∑ a 2 = ∑ a 2 + 2 ∑ a x + l x 2 \sum a^2=\sum a^2+2\sum a x+ lx^2 ∑a2=∑a2+2∑ax+lx2, ∑ b \sum b ∑b 和 ∑ b 2 \sum b^2 ∑b2不变, ∑ a b = ∑ a b + x ∑ b \sum ab=\sum ab+x\sum b ∑ab=∑ab+x∑b。
对于三四两个操作,可以认为是一个矩阵对其进行了操作。不妨令矩阵为 [ α β γ δ ] \begin{bmatrix} \alpha & \beta \\ \gamma & \delta \\ \end{bmatrix} [αγβδ],则这些元素变化如下:
- ∑ a = α ∑ a + γ ∑ b \sum a=\alpha \sum a+\gamma \sum b ∑a=α∑a+γ∑b。
- ∑ b = β ∑ a + δ ∑ b \sum b=\beta \sum a+\delta \sum b ∑b=β∑a+δ∑b。
- ∑ a 2 = α 2 ∑ a 2 + 2 α γ ∑ a b + γ 2 ∑ b 2 \sum a^2=\alpha^2 \sum a^2+ 2 \alpha \gamma \sum ab +\gamma^2 \sum b^2 ∑a2=α2∑a2+2αγ∑ab+γ2∑b2。
- ∑ b 2 = β 2 ∑ a 2 + 2 β δ ∑ a b + δ 2 ∑ b 2 \sum b^2=\beta^2 \sum a^2+ 2 \beta \delta \sum ab +\delta^2 \sum b^2 ∑b2=β2∑a2+2βδ∑ab+δ2∑b2。
- ∑ a b = α β ∑ a 2 + γ δ ∑ b 2 + ( α δ + β γ ) ∑ a b \sum ab=\alpha \beta \sum a^2+\gamma \delta \sum b^2+(\alpha \delta +\beta \gamma)\sum ab ∑ab=αβ∑a2+γδ∑b2+(αδ+βγ)∑ab。
这里再提一点:由于这里的修改操作过于复杂,因而可以考虑在 Pushdown 的时候直接调用修改函数本身来实现,避免代码的重复出现。
#include <cstdio>
#include <algorithm>
using namespace std;
const long long mod = 1000000007LL;
struct matrix
{
long long num[2][2];
void clear()
{
num[0][0] = num[1][1] = 1;
num[0][1] = num[1][0] = 0;
}
};
matrix operator *(matrix a,matrix b)
{
matrix ans;
for (int i = 0; i < 2;i++)
for (int j = 0; j < 2;j++)
{
ans.num[i][j] = 0;
for (int k = 0; k < 2;k++)
ans.num[i][j] = (ans.num[i][j] + a.num[i][k] * b.num[k][j] % mod) % mod;
}
return ans;
}
struct node
{
long long num[2];
long long squ[2];
long long ab;
long long lazy[2];
matrix tag;
};
struct node t[800005];
long long a[200005], b[200005];
int n;
void update(int place,int left,int right,int start,int end,matrix x);
void update(int place,int left,int right,int start,int end,bool flag,long long x);
void pushdown(int place,int left,int right)
{
int mid = (left + right) >> 1;
update(place << 1, left, mid, left, mid, t[place].tag);//利用现成的修改函数
update(place << 1 | 1, mid + 1, right, mid + 1, right, t[place].tag);
t[place].tag.clear();
update(place << 1, left, mid, left, mid, 0, t[place].lazy[0]);
update(place << 1 | 1, mid + 1, right, mid + 1, right, 0, t[place].lazy[0]);
t[place].lazy[0] = 0;
update(place << 1, left, mid, left, mid, 1, t[place].lazy[1]);
update(place << 1 | 1, mid + 1, right, mid + 1, right, 1, t[place].lazy[1]);
t[place].lazy[1] = 0;
}
void pushup(int place)
{
t[place].ab = (t[place << 1].ab + t[place << 1 | 1].ab) % mod;
t[place].num[0] = (t[place << 1].num[0] + t[place << 1 | 1].num[0]) % mod;
t[place].num[1] = (t[place << 1].num[1] + t[place << 1 | 1].num[1]) % mod;
t[place].squ[0] = (t[place << 1].squ[0] + t[place << 1 | 1].squ[0]) % mod;
t[place].squ[1] = (t[place << 1].squ[1] + t[place << 1 | 1].squ[1]) % mod;
}
void build(int place,int left,int right)
{
t[place].ab = 0;
t[place].lazy[0] = t[place].lazy[1] = 0;
t[place].squ[0] = t[place].squ[1] = 0;
t[place].num[0] = t[place].num[1] = 0;
t[place].tag.clear();
if(left==right)
{
t[place].ab = a[left] * b[left] % mod;
t[place].num[0] = a[left] % mod;
t[place].num[1] = b[left] % mod;
t[place].squ[0] = a[left] * a[left] % mod;
t[place].squ[1] = b[left] * b[left] % mod;
return;
}
int mid = (left + right) >> 1;
build(place << 1, left, mid);
build(place << 1 | 1, mid + 1, right);
pushup(place);
}
void update(int place,int left,int right,int start,int end,matrix x)//矩阵的修改
{
if (start <= left && right <= end)
{
//注意新老变量的覆盖问题,因而此处将老值全部存下来,防止被刷新
long long former_ab = t[place].ab;
t[place].ab = (t[place].squ[0] * x.num[0][0] % mod * x.num[0][1] % mod + t[place].squ[1] * x.num[1][0] % mod * x.num[1][1] % mod + t[place].ab * (x.num[0][0] * x.num[1][1] % mod + x.num[1][0] * x.num[0][1] % mod) % mod) % mod;
long long former_squ[2] = {t[place].squ[0], t[place].squ[1]};
long long former_num[2] = {t[place].num[0], t[place].num[1]};
long long former_lazy[2] = {t[place].lazy[0], t[place].lazy[1]};
t[place].squ[0] = (former_squ[0] * x.num[0][0] % mod * x.num[0][0] % mod + former_squ[1] * x.num[1][0] % mod * x.num[1][0] % mod + 2ll * x.num[0][0] % mod * x.num[1][0] % mod * former_ab % mod) % mod;
t[place].squ[1] = (former_squ[0] * x.num[0][1] % mod * x.num[0][1] % mod + former_squ[1] * x.num[1][1] % mod * x.num[1][1] % mod + 2ll * x.num[0][1] % mod * x.num[1][1] % mod * former_ab % mod) % mod;
t[place].num[0] = (former_num[0] * x.num[0][0] % mod + former_num[1] * x.num[1][0] % mod) % mod;
t[place].num[1] = (former_num[0] * x.num[0][1] % mod + former_num[1] * x.num[1][1] % mod) % mod;
t[place].lazy[0] = (former_lazy[0] * x.num[0][0] % mod + former_lazy[1] * x.num[1][0] % mod) % mod;
t[place].lazy[1] = (former_lazy[0] * x.num[0][1] % mod + former_lazy[1] * x.num[1][1] % mod) % mod;
t[place].tag = t[place].tag * x;
return;
}
pushdown(place, left, right);
int mid = (left + right) >> 1;
if(start<=mid)
update(place << 1, left, mid, start, end, x);
if(end>mid)
update(place << 1 | 1, mid + 1, right, start, end, x);
pushup(place);
}
void update(int place,int left,int right,int start,int end,bool flag,long long x)// 添加值修改
{
if(start<=left && right<=end)
{
t[place].lazy[flag] = (t[place].lazy[flag] + x) % mod;
t[place].squ[flag] = (t[place].squ[flag] + x * x % mod * (right - left + 1) % mod + 2ll * x % mod * t[place].num[flag] % mod) % mod;
t[place].ab = (t[place].ab + t[place].num[flag ^ 1] * x % mod) % mod;
t[place].num[flag] = (t[place].num[flag] + x * (right - left + 1) % mod) % mod;
return;
}
pushdown(place, left, right);
int mid = (left + right) >> 1;
if(start<=mid)
update(place << 1, left, mid, start, end, flag, x);
if(end>mid)
update(place << 1 | 1, mid + 1, right, start, end, flag, x);
pushup(place);
}
long long query(int place,int left,int right,int start,int end)
{
if(start<=left && right<=end)
return t[place].ab;
pushdown(place, left, right);
int mid = (left + right) >> 1;
long long ans = 0;
if(start<=mid)
ans = (ans + query(place << 1, left, mid, start, end)) % mod;
if(end>mid)
ans = (ans + query(place << 1 | 1, mid + 1, right, start, end)) % mod;
return ans;
}
int main()
{
int m, op;
scanf("%d", &n);
for (int i = 1; i <= n;i++)
scanf("%lld%lld", &a[i], &b[i]);
build(1, 1, n);
scanf("%d", &m);
matrix rev, work;
work.num[0][0] = work.num[0][1] = 3;
work.num[1][0] = 2;
work.num[1][1] = mod - 2;
rev.num[0][0] = rev.num[1][1] = 0;
rev.num[0][1] = rev.num[1][0] = 1;
while(m--)
{
int l, r, tag;
long long x;
scanf("%d", &op);
switch(op)
{
case 1:
{
scanf("%d%d%d%lld", &tag, &l, &r, &x);
update(1, 1, n, l, r, tag, x);
break;
}
case 2:
{
scanf("%d%d", &l, &r);
update(1, 1, n, l, r, work);
break;
}
case 3:
{
scanf("%d%d", &l, &r);
update(1, 1, n, l, r, rev);
break;
}
case 4:
{
scanf("%d%d", &l, &r);
printf("%lld\n", query(1, 1, n, l, r));
break;
}
}
return 0;
}
H I love exam
题意:一个还有 t t t( t ≤ 500 t \leq 500 t≤500) 天就要面对 n n n( n ≤ 50 n \leq 50 n≤50) 门期末考试的人对于即将要考的 n n n 门考试一概不知。为了挂科数不超过 p p p( p ≤ 3 p\leq 3 p≤3),别人给他推荐了 m m m 套复习宝典,每一个宝典有一个复习时间和提升分数。问在挂科数不超过 p p p 的情况下,最大获得总分为多少。
解法:一个简单的线性 DP。
首先预处理出每一门课提升到 w w w 分所需的最少时间。注意!当一个宝典作用分数为 3 3 3 分而他已经可以获得 99 99 99 分时,他这门课仍然只能获得 100 100 100分。此处就是一个压缩空间的 01 背包。
然后做 DP: f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k] 表示前 i i i 门课花了 j j j 天挂了 k k k 科所能获得的最大分数。我们只需要利用上面的预处理数组,直接枚举这门课获得多少分,再一次进行 01 背包即可。注意何时转移。
总体复杂度 0 ( 100 n t p ) 0(100ntp) 0(100ntp)。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <memory.h>
#include <map>
using namespace std;
int f[55][505][5];
int days[55][105];
struct node
{
int subject;
int score;
int day;
};
struct node que[15005];
int main()
{
int n, t, T, m, p;
scanf("%d", &T);
while(T--)
{
map<string, int> id;
int cnt = 0;
memset(f, -1, sizeof(f));
memset(days, 0x3f, sizeof(days));
scanf("%d", &n);
string temp;
for (int i = 1; i <= n;i++)
{
cin >> temp;
id[temp] = ++cnt;
}
scanf("%d", &m);
for (int i = 1; i <= m;i++)
{
cin >> temp >> que[i].score >> que[i].day;
que[i].subject = id[temp];
}
for (int i = 1; i <= n; i++)
days[i][0] = 0;
for (int i = 1; i <= m; i++)
{
int now = que[i].subject;
for (int j = 110; j >= que[i].score; j--)//由于一门课一个宝典不超过10分,因而可设上限为110,转移到的数组再对100取max
days[now][min(j, 100)] = min(days[now][min(j, 100)], days[now][min(100, j - que[i].score)] + que[i].day);
}
scanf("%d%d", &t, &p);
for (int i = 0; i <= t; i++)
f[0][i][0] = 0;
for (int i = 1; i <= n;i++)
{
// 该门课不及格,因而取分在0-59
for (int k = 1; k <= p;k++)
for (int l = 0; l <= 59; l++)
for (int j = t; j >= days[i][l]; j--)
if(f[i - 1][j - days[i][l]][k - 1] != -1)//只有当这个方案合法才能转移,因为最后有-1条件的存在
f[i][j][k] = max(f[i][j][k], f[i - 1][j - days[i][l]][k - 1] + l);
//及格
for (int k = 0; k <= p;k++)
for (int l = 60; l <= 100;l++)
for (int j = t; j >= days[i][l]; j--)
if(f[i - 1][j - days[i][l]][k] != -1)
f[i][j][k] = max(f[i][j][k], f[i - 1][j - days[i][l]][k] + l);
}
int ans = -1;
for (int k = 0; k <= p;k++)
ans = max(ans, f[n][t][k]);
printf("%d\n", ans);
}
return 0;
}
J I love permutation
题意:给定奇质数 p p p 和 a a a,满足 a < p a<p a<p,构造出长度为 p − 1 p-1 p−1 的序列 b x = a x m o d p b_x=ax \mod p bx=axmodp。问 { b n } \{b_n\} {bn} 的逆序对数目,对 2 2 2 取模。
解法:显然, { b n } \{b_n\} {bn} 一定为 [ 1 , p − 1 ] [1,p-1] [1,p−1] 的一个排列,因为这个序列中任意两个元素都不同余。由于是对 2 2 2 取模,因而考虑这个排列的奇偶性。
记排列为 π \pi π, s g n ( π ) = s g n ( ∏ 1 ≤ i , j ≤ n π ( i ) − π ( j ) i − j ) = s g n ( ∏ 1 ≤ i , j ≤ n a i − j i − j ) = s g n ( a p ( p − 1 ) 2 m o d p ) \displaystyle {\rm sgn }(\pi)={\rm sgn }(\prod_{1 \leq i , j \leq n} \frac{\pi(i)-\pi(j)}{i-j})={\rm sgn }(\prod_{1 \leq i , j \leq n} a\frac{i-j}{i-j})={\rm sgn }(a^{\frac{p(p-1)}{2}} \mod p) sgn(π)=sgn(1≤i,j≤n∏i−jπ(i)−π(j))=sgn(1≤i,j≤n∏ai−ji−j)=sgn(a2p(p−1)modp)。显然, p p p 为奇数,则 s g n ( a p ( p − 1 ) 2 m o d p ) {\rm sgn }(a^{\frac{p(p-1)}{2}} \mod p) sgn(a2p(p−1)modp) 与 s g n ( a ( p − 1 ) 2 m o d p ) {\rm sgn }(a^{\frac{(p-1)}{2}} \mod p) sgn(a2(p−1)modp) 正负性相同。因而判定 s g n ( a ( p − 1 ) 2 m o d p ) {\rm sgn }(a^{\frac{(p-1)}{2}} \mod p) sgn(a2(p−1)modp) 为 1 1 1 还是 − 1 -1 −1 即可。
注意模数较大,因而乘的时候可以考虑使用 int128 或者龟速乘。
#include <bits/stdc++.h>
using namespace std;
__int128_t p;
__int128_t power(__int128_t a,long long x)
{
__int128_t ans = 1;
while(x)
{
if(x&1)
ans = ans * a % p;
a = a * a % p;
x >>= 1;
}
return ans;
}
int main()
{
int t;
long long a;
scanf("%d", &t);
while(t--)
{
scanf("%lld%lld", &a, &p);
if((long long)power(a,(p-1)/2)==p-1)
printf("1\n");
else
printf("0\n");
}
return 0;
}
K I love max and multiply
题意:给定两个长度为 n n n 的序列 A i , B i A_i,B_i Ai,Bi,构造一个新的长度为 n n n 的序列 C k = max i & j ≥ k { A i B j } C_k=\max_{i \& j \geq k} \{A_iB_j\} Ck=maxi&j≥k{AiBj}。求 C i C_i Ci 的和对大质数取模。
解法:注意!此题中数列可以有负数,因而不能直接取大值。
维护几个数组: f i f_i fi 表示二进制组成中包含 i i i 的全部正数 A j A_j Aj 的最大值,即 i & j = i i \& j=i i&j=i, g i g_i gi 表示包含 i i i 的正数 B j B_j Bj 最大值。 s i s_i si 则表示 A j A_j Aj 负数最小, t i t_i ti 表示 B j B_j Bj 中负数最小。上式均可由 O ( n log n ) O(n\log n) O(nlogn) 进行递推。最后统计答案,取 f i g i f_ig_i figi 和 s i t i s_it_i siti 的最大值即可。