2023年最后一哆嗦 题解

2023年最后一哆嗦

一边改题一边就给题解写了。

T1

这题看上去就像$exgcd$的题。

这种求间隔的题,先给方程列出来,再考虑解。

设火车在走过$m$个完整的来回之后可成立,走完$m$个完整的来回后走过$x$个时间到达了三班,又停止了$n$秒的同时乔琨醒来。

同理,设乔琨睡与醒过了$a$个完整的来回,$a$个完整的来回后又睡了$p$秒,再经过$b$秒时火车恰好到了三班。

关于$m,n,a,b$四个变量的范围,$m,a$二者是没有限制的,但$0\le n < y$,$0\le b < q$。

那么方程是很好列出来的:

$2 \times (x+y) \times m+x+n=a \times(p+q)+p+b$

考虑变为$\ exgcd\ $中$ax+by=c$的形式。

$2\times (x+y)\times m -(p+q) \times a=(p+b)-(x+n)$

观察到$y\le500,q\le500,$我们可以暴力枚举$y,q$两个变量,不断检查并更新最小解。

代码就很好写了。

//式子:2×(x+y)×m−(p+q)×a=(p+b)-(x+n)
#include <bits/stdc++.h>
#define int long long
using namespace std;
int exgcd(int &x, int &y, int a, int b) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int G = exgcd(x, y, b, a % b);
    int t = x;
    x = y;
    y = t - a / b * y;
    return G;
}
signed main() {
    ios::sync_with_stdio(0);
    int T;
    cin >> T;
    while (T--) {
        int ans = 2e18;
        int x, y, p, q;
        cin >> x >> y >> p >> q;
        int a = 2 * (x + y), b = p + q;
        int X, Y;
        int G = exgcd(X, Y, a, b);
        // 枚举 b, n
        bool glg = true;
        for (int N = 0; N < y; N++) {
            for (int B = 0; B < q; B++) {
                int c = (p + B) - (x + N);
                if (c % G != 0)
                    continue;
                glg = false;
                int XX = X * (c / G);//ax + by = c的一组解
                XX = (XX % ((p + q) / G) + ((p + q) / G)) % ((p + q) / G);
                ans = min(ans, a * XX + x + N);
            }
        }
        if (glg)
            puts("infinity");
        else
            cout << ans << "\n";
    }
    return 0;
}

T2

求 $gcd(i,j)=k\ \ (k∈P,\ 1 \le i,\ j\le n)\ \ $

由$gcd$的结论,答案变形为$gcd(i,j)=1(1\le i,\ j\le \lfloor \frac{n}{k} \rfloor)$,这样每个$i$对答案的贡献就是$\phi(i)$

那么预先筛出质数,再枚举求出答案即可。

注意求答案时的细节问题。

#include <bits/stdc++.h>
#define int long long
#define N 10000007
using namespace std;
vector<int>v;
int phi[N];
bitset<N>pri;
int n;
void get_p() {
    phi[1] = 1;
    for (int i = 2; i <= n; i++) {
        if(!pri[i]) {
            v.push_back(i);
            phi[i] = i - 1;
        }
        for(int j = 0; j < (int)v.size() && v[j] * i <= n; j++) {
            pri[i * v[j]] = true;
            if(i % v[j] == 0) {
                phi[i * v[j]] = phi[i] * v[j];
                break;
            }
            phi[i * v[j]] = phi[i] * phi[v[j]];
        }
    }
}
int sum[N];
int res;
signed main() {
    scanf("%lld", &n);
    get_p();
    for (int i = 1;  i <= n; i++)
        sum[i] = sum[i - 1] + phi[i];
    for(int j = 0; j < (int)v.size(); j++)
        res += 2 * sum[n / v[j]] - 1;
    cout << res << "\n";
    return 0;
}

T3

大水题。

考虑可行情况=所有情况-不可行情况。

这里的所有情况就是$10^n$,不可行情况就是没有9和没有0的,即$2\times9^n$,注意最终要容斥一下,加回来9和0都有的,这部分是$8^n$。

因此最终答案是$10^n-2\times9^n+8^n$

代码懒得放了。

T4

考虑一个显然正确的结论:

对于两个点$i,j$,若$a_i>a_j$,则$i$移动的距离必然不比$j$小。

这个结论应当是显然的。如果不满足,必然还会有另一种移动方案使它不成为最优解。

那么对于权值最大的点$i$,我们必然会将它塞到空座位堆里的最左端或最右端,因为最左端或最右端是离它最远的点。

有了这个结论,就好做了。

贪心!!

贪心个辣子。对于一个点,你无法确定它给最左边放还是给最右边放。再何况,$N \le 2000$,$O(n)$的贪心,出题人不会放你过的。

根据上面的思路,相对较大的节点始终会在空位置的两端,因此相对小的节点始终会在空位置的中央并连成一个区间

这不就是区间$DP$的题目吗?

$DP$,启动!我们将节点按权值从小给大依次排序,定义$dp[i][j]$为第$1$个节点至第$j-i+1$个节点给区间$[i,j]$填时的最大答案。

那么状态只能由它的两边转移而来。$dp$方程是显然的:$dp[i][j]=max(dp[i+1][j]+abs(pos[j-i+1]-i)\times val[j-i+1],dp[i][j-1]+abs(pos[j-i+1]-j)\times val[j-i+1])$

代码很好写。

记着开$long\ long$。

#include <bits/stdc++.h>
#define N 2005
using namespace std;
long long dp[N][N];
struct Node {
    int pos;
    long long val;
} e[N];
bool operator < (const Node &a, const Node &b) {
    return a.val < b.val;
}
int n;
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        e[i].pos = i;
        scanf("%lld", &e[i].val);
    }
    sort(e + 1, e + 1 + n);
    for (int len = 1; len <= n; len++)
        for (int i = 1; i + len - 1 <= n; i++) {
            int j = i + len - 1;
            dp[i][j] = max(dp[i + 1][j] + abs(e[j - i + 1].pos - i) * e[j - i + 1].val,
                           dp[i][j - 1] + abs(e[j - i + 1].pos - j) * e[j - i + 1].val);

        }
    cout << dp[1][n] << "\n";
    return 0;
}

T5

小清新思维题

为了方便转化成题目所求的样子,我们先让每个$a[i]$转化成$h-a[i]$,也就是题目要求的作加法数量。

转化完之后的序列不好处理,考虑差分解。此时我们发现,目标情况就是差分序列全为$0$。

题目中说一个点只能作左/右端点各一次,因此当这个差分序列的两相邻元素的差值的绝对值大于1时,直接判断无解并输出。

那么差分序列的值只有三种情况:$-1,1,0$。我们分类$nei$讨论$nen$。

为了方便统计答案,我们记当前可用左节点的数目为$cnt$,最终的答案为$ans$。

  1. 当$d_i=-1$时:

此时序列中的原值$a_i$是小于$a_{i+1}$的,因此$i$会成为一个左端点,则$cnt++$。

  1. 当$d_i=1$时:

此时序列中的原值$a_i$是大于$a_{i+1}$的,因此$i$会成为一个右端点。这个右端点可以匹配可用的任意一个左端点,因此$ans = ans \times cnt$。但同时会消耗掉一个可用的左节点,因此$cnt--$。

  1. 当$d_i=0$时:

此时序列中的原值$a_i$是等于$a_{i+1}$的。显然我们可以不用将它作为一个左/右节点,这是一种情况。异或是我们让它既作为一个左节点,又作为一个右节点,此时右$cnt$个左节点和它搭配,因此$ans=ans \times(num+1)$。同时这样做是不会对左节点的个数造成影响的因为它在匹配了一个左节点的同时自己成为了 一个左节点。

没了。

代码是一如既往地好写:

#include <bits/stdc++.h>
#define N 2005
#define mod 1000000007
using namespace std;
int a[N];//原序列
int d[N];//差分序列
int n, h;
int main() {
    scanf("%d%d", &n, &h);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        a[i] = h - a[i];
    }
    for (int i = 1; i <= n + 1; i++) {
        d[i] = a[i] - a[i - 1];
        if (abs(d[i]) > 1) {
            puts("0");
            return 0;
        }
    }
    long long ans = 1, cnt = 0;
    for (int i = 1; i <= n; i++)
        if(d[i] == 1)
            cnt++;
        else if(d[i] == -1)
            ans = ans * cnt % mod, cnt--;
        else
            ans = ans * (cnt + 1) % mod;
    printf("%lld\n", ans);
    return 0;
}

T6

这种涉及路径统计的东西,往往和$dp$脱不了干系。

$2 ≤ H, W ≤ 10^9$,完全不用考虑设“点”这一维了。

又看到$1≤K≤10^6$,因此考虑$dp$这一维。

“车”走横竖,因此我们在$dp$时,分类$nei$讨论$nen$。我们发现,和终点的关系共有四种情况:

  1. 在终点

  2. 与终点同行不同列

  3. 与终点同列不同行

  4. 与终点不同列也不同行

上面的情况分别设为 $dp[i][0/1/2/3]$。状态定义好之后,转移方程就是显然的了。(鉴于大众个人习惯,行列不使用$H,W$,而使用$m,n$)。

$dp[i][0]=dp[i-1][1]+dp[i-1][2]$

$dp[i][1]=dp[i-1][0]\times(n-1)+dp[i-1][1]\times(n-2)+dp[i-1][3]$

$dp[i][2]=dp[i-1][0]\times(m-1)+dp[i-1][2]\times(m-2)+dp[i-1][3]$

$dp[i][3]=dp[i-1][1]\times(m-1)+dp[i-1][2]\times(n-1)+(m+n-4)\times dp[i-1][3]$

初始化:$dp[0][0/1/2/3]=1$。(这里的第二维取决于其位置)。

代码就很好写了。

#include <bits/stdc++.h>
#define int long long
#define N 1000005
#define mod 998244353
using namespace std;
int n, m, k;
int dp[N][4];
signed main() {
    int x_1, x_2, y_1, y_2;
    cin >> m >> n >> k >> x_1 >> y_1 >> x_2 >> y_2;
    if(x_1 == x_2 && y_1 == y_2)
        dp[0][0] = 1;
    else if(x_1 == x_2 && y_1 != y_2)
        dp[0][1] = 1;
    else if(x_1 != x_2 && y_1 == y_2)
        dp[0][2] = 1;
    else
        dp[0][3] = 1;
    for (int i = 1; i <= k; i++) {
        dp[i][0] = (dp[i - 1][1] % mod + dp[i - 1][2] % mod) % mod;
        dp[i][1] = ((dp[i - 1][0] % mod * (n - 1) % mod + dp[i - 1][1] % mod * (n - 2) % mod) % mod + dp[i - 1][3] % mod) % mod;
        dp[i][2] = ((dp[i - 1][0] % mod * (m - 1) % mod + dp[i - 1][2] % mod * (m - 2) % mod) % mod + dp[i - 1][3] % mod) % mod;
        dp[i][3] = ((dp[i - 1][1] % mod * (m - 1) % mod + dp[i - 1][2] % mod * (n - 1) % mod) % mod + dp[i - 1][3] % mod * (m + n - 4) % mod) % mod;
    }
    cout << dp[k][0] << "\n";
    return 0;
}

$End.$

posted @ 2024-01-01 14:26  长安19路  阅读(37)  评论(0)    收藏  举报  来源