2023年最后一哆嗦 题解

2023年最后一哆嗦

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

T1

这题看上去就像exgcd的题。

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

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

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

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

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

2×(x+y)×m+x+n=a×(p+q)+p+b

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

2×(x+y)×m(p+q)×a=(p+b)(x+n)

观察到y500,q500,我们可以暴力枚举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  (kP, 1i, jn)  

gcd的结论,答案变形为gcd(i,j)=1(1i, jnk),这样每个i对答案的贡献就是ϕ(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

大水题。

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

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

因此最终答案是10n2×9n+8n

代码懒得放了。

T4

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

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

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

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

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

贪心!!

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

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

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

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

那么状态只能由它的两边转移而来。dp方程是显然的:dp[i][j]=max(dp[i+1][j]+abs(pos[ji+1]i)×val[ji+1],dp[i][j1]+abs(pos[ji+1]j)×val[ji+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]转化成ha[i],也就是题目要求的作加法数量。

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

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

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

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

  1. di=1时:

此时序列中的原值ai是小于ai+1的,因此i会成为一个左端点,则cnt++

  1. di=1时:

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

  1. di=0时:

此时序列中的原值ai是等于ai+1的。显然我们可以不用将它作为一个左/右节点,这是一种情况。异或是我们让它既作为一个左节点,又作为一个右节点,此时右cnt个左节点和它搭配,因此ans=ans×(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脱不了干系。

2H,W109,完全不用考虑设“点”这一维了。

又看到1K106,因此考虑dp这一维。

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

  1. 在终点

  2. 与终点同行不同列

  3. 与终点同列不同行

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

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

dp[i][0]=dp[i1][1]+dp[i1][2]

dp[i][1]=dp[i1][0]×(n1)+dp[i1][1]×(n2)+dp[i1][3]

dp[i][2]=dp[i1][0]×(m1)+dp[i1][2]×(m2)+dp[i1][3]

dp[i][3]=dp[i1][1]×(m1)+dp[i1][2]×(n1)+(m+n4)×dp[i1][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 @   长安19路  阅读(17)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示