A.Portal

题意:

给定一个个点条边的带权图。一开始在号点,要按顺序完成个任务,第个任务是先去再走 到。当走到一个点上的时候,可以在这个点创建一个传送门。当同时存在两个传送门的时候, 可以在传送门之间不耗代价地传送。如果已经存在了两个传送门,想再创建一个,就必须选择之前的一个传送门关掉(关掉这个操作不耗时间,并且是远程操作,不需要走过去)。问完成所有任务的最短总行走距离。

题解:

表示当前已经完成了前个任务,当前正在上,传送门的位置在
可以证明,只需要3种转移,就可以覆盖所有情况:

  1. 直接从走到
  2. 枚举走到之后,传送门的位置变为了哪个节点,设这个节点是。从走到,在设置传送门,从传送到,再从走到
  3. 传送到,从走到,在设置传送门,最后从走到
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 305;
const ll INF = 0x3f3f3f3f3f3f3f3fll;
ll f[maxn * 2][maxn], d[maxn][maxn];
int a[maxn * 2];
int main()
{
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    memset(d, 0x3f, sizeof(d));
    memset(f, 0x3f, sizeof(f));
    for (int i = 1; i <= n; i++)
        d[i][i] = 0;
    for (int i = 1; i <= m; i++)
    {
        int u, v;
        ll w;
        scanf("%d%d%lld", &u, &v, &w);
        d[u][v] = d[v][u] = min(d[u][v], w);
    }
    a[1] = 1;
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &a[2 * i], &a[2 * i + 1]);
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
    for (int i = 1; i <= n; i++)
        f[1][i] = d[1][i];
    for (int i = 2; i <= 2 * k + 1; i++)
        for (int p = 1; p <= n; p++)
        {
            f[i][p] = min(f[i][p], f[i - 1][p] + d[a[i]][a[i - 1]]);
            for (int q = 1; q <= n; q++)
            {
                f[i][q] = min(f[i][q], f[i - 1][p] + d[a[i - 1]][q] + d[p][a[i]]);
                f[i][q] = min(f[i][q], f[i - 1][p] + d[p][q] + d[q][a[i]]);
            }
        }
    ll ans = INF;
    for (int i = 1; i <= n; i++)
        ans = min(ans, f[2 * k + 1][i]);
    printf("%lld\n", ans);
}

B.Graph

题意:

给一棵个节点的树,每条边有边权。可以任意加边和删边,但要满足任何时刻图连通,而且任何一个环的边权异或和为。求操作后最小权值和。

题解:

可以发现任意两个点之间连边的权值都是固定的。由于图始终联通,所以两点间始终存在至少一条路径,如果存在多条,根据环的异或和为0,两点间的路径的异或和应该相等,且始终是固定的。可以通过给定一个根节点权值为,其余点通过和边权异或得到一个点权,任意两个点的边权就是两个点权异或的值。因此问题就转化为已知一个个节点的完全图,要求一棵权值之和最小的树。就是求最小生成树,或者叫异或最小生成树,考虑到是完全图,可以想到是算法。根据的思想找最小生成树,很自然得想到把所有点分为最高位为的堆,其中找一条权值最小的边连接两堆,其余的点都在同一堆内找一条边连接,这个可以用字典树来维护

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
const int maxp = maxn * 32;
int head[maxn], tot = 1;
int a[maxn];
ll ans;
struct Edge
{
    int to, nxt, w;
} e[maxn << 1];
void add(int u, int v, int w)
{
    e[++tot].to = v;
    e[tot].nxt = head[u];
    e[tot].w = w;
    head[u] = tot;
}
struct trie
{
    int son[maxp][2];
    int root, tot;
    void init()
    {
        for (int i = 0; i <= tot; i++)
            son[i][0] = son[i][1] = 0;
        root = tot = 1;
    }
    void insert(int w)
    {
        int x = root;
        for (int i = 29; i >= 0; i--)
        {
            int &y = son[x][w >> i & 1];
            if (!y)
                y = ++tot;
            x = y;
        }
    }
    int findx(int w)
    {
        int x = root, ec = 0;
        for (int j = 29; j >= 0; j--)
        {
            int k = w >> j & 1;
            if (son[x][k])
                x = son[x][k];
            else
                x = son[x][!k], ec |= 1 << j;
        }
        return ec;
    }
} tr;
void dfs(int id, int l, int r)
{

    if (id < 0)
        return;
    int mid = l - 1;
    for (int i = l; i <= r; i++)
        if (((a[i] >> id) & 1) == 0)
            mid = i;
    if (l <= mid)
        dfs(id - 1, l, mid);
    if (mid + 1 <= r)
        dfs(id - 1, mid + 1, r);
    if (l <= mid && mid + 1 <= r)
    {
        for (int i = l; i <= mid; i++)
            tr.insert(a[i]);
        int mi = 2e9;
        for (int i = mid + 1; i <= r; i++)
            mi = min(mi, tr.findx(a[i]));
        ans += mi;
    }
    tr.init();
}
void dfs(int x, int fat)
{
    for (int i = head[x]; i; i = e[i].nxt)
    {
        int y = e[i].to, w = e[i].w;
        if (y == fat)
            continue;
        a[y] = a[x] ^ w;
        dfs(y, x);
    }
}
int main()
{
    int n, u, v, w;
    scanf("%d", &n);
    for (int i = 1; i < n; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        u++;
        v++;
        add(u, v, w);
        add(v, u, w);
    }
    dfs(1, 0);
    sort(a + 1, a + 1 + n);
    dfs(29, 1, n);
    printf("%lld\n", ans);
    return 0;
}

C.Easy

题意:

有两个长度为的正整数序列满足
的数量

题解:

这题看题解也没看懂,找了一篇现在也没搞懂,先贴出来把戳我~

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 1e6+5;
const ll mod = 998244353;
ll qpow(ll a, ll b){
    ll res = 1;
    a %= mod;
    while(b) {
        if(b & 1) {
            res = res * a % mod;
        }
        b >>= 1;
        a = a * a % mod;
    }
    return res;
}
ll fac[maxn], inv[maxn];
void init() {
    fac[0] = 1;
    for(int i = 1; i < maxn; i++) {
        fac[i] = fac[i - 1] * i % mod;
    }
    inv[maxn - 1] = qpow(fac[maxn - 1], mod - 2);
    for(int i = maxn - 2; i >= 0; i--) {
        inv[i] = inv[i + 1] * (i + 1) % mod;
    }
}
ll c(ll n, ll m) {
    if(m > n) {
        return 0;
    }
    if(m == 0) return 1;
    if(n < maxn) return fac[n] * inv[m] % mod * inv[n - m] % mod;
    ll res = inv[m];
    for(int i = n - m + 1; i <= n; i++) {
        res = res * i % mod;
    }
    return res;
}
int main()
{
    init();
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int n, m, k;
        scanf("%d%d%d", &n, &m, &k);
        if (n > m)
            swap(n, m);
        ll res = 0;
        for (int i = 0; i <= n - k; i++)
            res = ((ll)res + c(k + i - 1, i) * c(k + n - k - i - 1, n - k - i) % mod * c(k + m - k - i - 1, m - k - i) % mod) % mod;
        printf("%lld\n", res);
    }
}

D.Drop Voicing

题意:

给定一个的排列,有两种操作

  1. 可以将倒数第二个数放到开头
  2. 可以将开头的第一个数放到最后

续若干次操作(包括1次)称为一段。现在要将排列变成,要使得段数尽可能少,输出这个最小值。

题解:

相当于看成一个环,连续若干次操作等价为改变指针指向的数所处的位置,连续若干次操作等价为改变指针的位置。因此可以在环上找到一个最长上升序列,这个序列的数不需要改变,其余的改变,可以发现这个是最优的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 505;
const int INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
int a[maxn << 1], d[maxn];
int LIS(int *a, int n)
{
    for (int i = 1; i <= n; i++)
        d[i] = 0;
    int lis = 0;
    for (int i = 1; i <= n; i++)
    {
        if (a[i] > d[lis])
            d[++lis] = a[i];
        else
            *lower_bound(d, d + lis, a[i]) = a[i];
    }
    return lis;
}
int main()
{
    int n, ans = 0;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]), a[i + n] = a[i];
    for (int i = 0; i < n; i++)
    {
        int len = LIS(a + i, n);
        ans = max(len, ans);
    }
    printf("%d\n", n - ans);
    return 0;
}

E.Bogo Sort

题意:

给定置换,求有多少排列可以通过这个置换变成顺序。

题解:

首先可以确定整个给定的排列可以组成若干个独立的环。观察可以发现答案就为这些环长度的

from math import gcd
n=int(input())
p=[]
p=list(map(int,input().split()))
p.insert(0,0)
vis=[(False) for i in range(n+10)]
v=[]
for i in range(1,n+1):
    if vis[i]==True:
        continue
    vis[i]=True
    tmp=p[i]
    ans=1
    while tmp!=i:
        vis[tmp]=True
        tmp=p[tmp]
        ans+=1
    v.append(ans)
v.sort(reverse=True)
res=v[0]
for i in v[1:]:
    res=res*i//gcd(res,i)
res%=10**n
print(res)

F.DPS

题意:

模拟显示游戏中的伤害数据。

题解:

签到题,模拟即可

#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
int a[maxn];
typedef long long ll;
int main(){
    int n;
    cin>>n;
    int mx=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        mx=max(a[i],mx);
    }
    for(int i=1;i<=n;i++){
        ll ans=(1ll*50*a[i]+mx-1)/mx;
        cout<<"+";
        for(int j=1;j<=ans;j++)cout<<"-";
        cout<<"+\n";
        cout<<"|";
        for(int j=1;j<ans;j++)cout<<" ";
        if(mx==a[i])cout<<"*";
        else if(a[i]!=0)cout<<" ";
        cout<<"|"<<a[i]<<"\n";
        cout<<"+";
        for(int j=1;j<=ans;j++)cout<<"-";
        cout<<"+\n";
    }
    return 0;
}

H.Interval

题意:

给定个数,定义

给定个询问,每个询问包含一个,其中为上一次询问的结果,要求求出对应的的集合个数

题解:

考虑到答案异或,并且,因此对应每个固定的右端点,他的值至多为个,因此可以固定右端点来统计左端点即可,二维偏序,对每个右端点的结果用主席树来维护,对于左端点的就是的区间的结果。要注意的是因为用主席树每次会继承上一棵树的结果,所以对于同一个值可能会重复,因此只需要每次记录每个值出现的最新的坐标记录下来,每次更新都令最近位置的坐标,当前位置的值即可,具体实现见代码。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 5;
typedef pair<int, int> pii;

struct Node
{
    int l, r, val;
} T[maxn << 5];
int tot, rt[maxn];

void update(int &p, int l, int r, int pos, int val)
{
    T[++tot] = T[p], p = tot, T[p].val += val;
    if (l == r)
        return;
    int mid = l + r >> 1;
    if (pos <= mid)
        update(T[p].l, l, mid, pos, val);
    else
        update(T[p].r, mid + 1, r, pos, val);
}
int query(int p, int l, int r, int L, int R)
{
    if (l > R || L > r)
        return 0;
    if (L <= l && r <= R)
        return T[p].val;
    int mid = l + r >> 1;
    return query(T[p].l, l, mid, L, R) + query(T[p].r, mid + 1, r, L, R);
}

int main()
{
    map<int, int> now, mp;
    int n;
    scanf("%d", &n);
    for (int i = 1, x; i <= n; i++)
    {
        map<int, int> tmp;
        scanf("%d", &x);
        now[(1 << 30) - 1] = i;
        for (auto p : now)
        {
            int k = p.first & x;
            tmp[k] = max(tmp[k], p.second);
        }
        rt[i] = rt[i - 1];
        for (auto p : tmp)
        {
            if (mp.count(p.first))
                update(rt[i], 1, n, mp[p.first], -1);
            mp[p.first] = p.second;
            update(rt[i], 1, n, p.second, 1);
        }
        swap(tmp, now);
    }
    int q, last = 0, l, r;
    scanf("%d", &q);
    while (q--)
    {
        scanf("%d%d", &l, &r);
        l = (l ^ last) % n + 1;
        r = (r ^ last) % n + 1;
        if (l > r)
            swap(l, r);
        last = query(rt[r], 1, n, l, r);
        printf("%d\n", last);
    }
    return 0;
}

I.Hard Math Problem

题意:

有一个无穷大的二维网格,每个格子可以是,每个旁边要有一个3$,要使机器的占比最大。

题解:

发现交错是最优的,此时答案为

#include <bits/stdc++.h>
using namespace  std;
const int maxn=1e7+10;
bool prime[maxn];
int mp[maxn],res[maxn][3];
typedef long long ll;
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
int main()
{
    printf("%.6lf\n",2.0/3);
    return 0;
}

官方题解