杭电多校第二场7月22日补题记录

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),可能执行以下四种操作:

  1. 求出 ∑ i = l r a i b i \displaystyle \sum_{i=l}^{r} a_ib_i i=lraibi
  2. 给定参数 p p p,若 p p p 0 0 0 则将区间 [ l , r ] [l,r] [l,r] 内的所有 a a a 值增加 x x x,否则增加 b b b 的值。
  3. 将区间 [ 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,3a2b)
  4. 将区间 [ 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} [3232];而第四个操作是乘以了 [ 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+2ax+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+xb

对于三四两个操作,可以认为是一个矩阵对其进行了操作。不妨令矩阵为 [ α β γ δ ] \begin{bmatrix} \alpha & \beta \\ \gamma & \delta \\ \end{bmatrix} [αγβδ],则这些元素变化如下:

  1. ∑ a = α ∑ a + γ ∑ b \sum a=\alpha \sum a+\gamma \sum b a=αa+γb
  2. ∑ b = β ∑ a + δ ∑ b \sum b=\beta \sum a+\delta \sum b b=βa+δb
  3. ∑ 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=α2a2+2αγab+γ2b2
  4. ∑ 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=β2a2+2βδab+δ2b2
  5. ∑ 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 t500) 天就要面对 n n n n ≤ 50 n \leq 50 n50) 门期末考试的人对于即将要考的 n n n 门考试一概不知。为了挂科数不超过 p p p p ≤ 3 p\leq 3 p3),别人给他推荐了 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 p1 的序列 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,p1] 的一个排列,因为这个序列中任意两个元素都不同余。由于是对 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(1i,jnijπ(i)π(j))=sgn(1i,jnaijij)=sgn(a2p(p1)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(p1)modp) s g n ( a ( p − 1 ) 2 m o d    p ) {\rm sgn }(a^{\frac{(p-1)}{2}} \mod p) sgn(a2(p1)modp) 正负性相同。因而判定 s g n ( a ( p − 1 ) 2 m o d    p ) {\rm sgn }(a^{\frac{(p-1)}{2}} \mod p) sgn(a2(p1)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&jk{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 的最大值即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值