BITACM2018第二轮积分赛(一)题解

A - 一道可持久化并查集好题

出题人: z h b e r zhber zhber

通过/提交: 17 / 37 17/37 17/37

题解: 此题数据极小,直接模拟即可。撤销操作就是不执行前面 k k k 次操作,把撤销操作视为返回 k k k 次之前的历史版本。用state[i][j][k] 表示第 i i i 次操作后第 j j j 个点和第 k k k 个点是否联通,对于 1 1 1 操作,复制上一次操作之后的状态,然后考虑新增的经过 a − b a-b ab 边而可到达的点对。这些新点对(记为x^y)一定是 x x x 在一侧, y y y 在另一侧,由于新加了 a − b a-b ab 而联通。那么分别找 a , b a,b a,b 能到达的点放到两集合内,取两个集合各一点配对都设为联通即可 O ( n 2 ) O(n^2) O(n2) 。或者直接floyd传递闭包 O ( n 3 ) O(n^3) O(n3) 。对于第 2 2 2 种操作,复制上一次的状态,暴力找和它联通的点数。对于第三种操作,复制的不是上一次的状态,而是第 x − k − 1 x-k-1 xk1 次的状态。时间复杂度 O ( n 2 m ) O(n^2m) O(n2m) ,空间复杂度 O ( n 2 m ) O(n^2m) O(n2m)

#include<bits/stdc++.h>
#define maxn 100 + 10
using namespace std;
int state[maxn][maxn][maxn];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) state[0][i][i]=1;
    for (int i=1;i<=m;i++)
    {
        int op;scanf("%d",&op);
        if (op==1)
        {
            for (int j=1;j<=n;j++)
                for (int k=1;k<=n;k++)
                    state[i][j][k]=state[i-1][j][k];
            int a,b;scanf("%d%d",&a,&b);
            vector<int>A,B;A.clear();B.clear();
            for (int j=1;j<=n;j++)
            {
                if (state[i][a][j])A.push_back(j);
                if (state[i][b][j])B.push_back(j);
            }
            for (int j=0;j<A.size();j++)
                for (int k=0;k<B.size();k++)
                {
                    state[i][A[j]][B[k]]=1;
                    state[i][B[k]][A[j]]=1;
                }
        }else if (op==2)
        {
            for (int j=1;j<=n;j++)
                for (int k=1;k<=n;k++)
                    state[i][j][k]=state[i-1][j][k];
            int a,sum=0;scanf("%d",&a);
            for (int j=1;j<=n;j++)if (state[i][j][a])sum++;
            printf("%d\n",sum);
        }else if (op==3)
        {
            int k;scanf("%d",&k);
            for (int j=1;j<=n;j++)
                for (int l=1;l<=n;l++)
                    state[i][j][l]=state[i-k-1][j][l];
        }
    }
}

B - zhb love math

出题人: s t r a w b e r r y strawberry strawberry

通过/提交: 1 / 2 1/2 1/2

题解: 考虑每个元素 b i b_i bi 对答案的贡献,一定是一些包含 b i b_i bi 的区间,且区间除了它以外,没有 b j b_j bj b i b_i bi 的因数。容易想到的是,对每个 b i b_i bi 找到包含它可行区间的最左边 l i l_i li 和包含它可行区间的最右边 r i r_i ri 那么任何区间 [ l , r ]   l , r ∈ [ l i , r i ] [l,r]\ l,r∈[l_i,r_i] [l,r] l,r[li,ri] 都是答案,这样的区间有 ( i − l i + 1 ) × ( r i − i + 1 ) (i-l_i+1)×(r_i-i+1) (ili+1)×(rii+1) 个,朴素的 O ( n 2 ) O(n^2) O(n2) 想法是从 i i i 开始往左往右扫,找到第一个 b j % b i = 0 b_j\%b_i=0 bj%bi=0 j j j 就是边界,因为 n ≤ 1 0 5 n\leq 10^5 n105,是不能通过这题的。考虑怎么优化,当枚举到 i i i 的时候, 对于 i i i 左侧的 b j b_j bj 如果 b j % b i = 0 b_j\%b_i=0 bj%bi=0 那么对于位置 j j j r j r_j rj 最右只能到 ( i − 1 ) (i-1) (i1) 的位置!, 因为 b i ≤ 1 0 4 b_i\leq 10^4 bi104 ,所以可以直接枚举 b i b_i bi 的倍数 j j j 去更新答案,从左往右扫以 pre[i] 表示 i i i 最后出现的位置,对于 b i b_i bi 如果出现过其倍数 j j j,就有 r[pre[j]]=i-1 。同理扫着扫一遍对 l l l 维护一个 last[i] 数组就能得到答案。时间复杂度 O ( n l o g m ) O(nlogm) O(nlogm) ,空间复杂度 O ( n + m ) O(n+m) O(n+m)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
/* head */
int a[maxn], l[maxn], r[maxn], pre[maxn], last[maxn];
void solve() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        l[i] = 1, r[i] = n;
    }
    memset(pre, 0, sizeof pre);
    memset(last, 0, sizeof last);
    for (int i = 1; i <= n; i++) {
        for (int j = a[i]; j <= 10000; j += a[i]) {
            if (pre[j] != 0 && r[pre[j]] == n) r[pre[j]] = i - 1;
        }
        pre[a[i]] = i;
    }
    for (int i = n; i >= 1; i--) {
        for (int j = a[i]; j <= 10000; j += a[i]) {
            if (last[j] != 0 && l[last[j]] == 1) l[last[j]] = i + 1;
        }
        last[a[i]] = i;
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++) ans = (ans + 1LL * (i - l[i] + 1) * (r[i] - i + 1) % mod) % mod;
    printf("%lld\n", ans);
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) solve();
    return 0;
}

C - 旅かえる

出题人: F S M M FSMM FSMM

通过/提交: 28 / 130 28/130 28/130

题解: 将问题反过来看就是问第 n n n 片荷叶到第 i i i 片荷叶所需要花的最短时间。就是一个简单的最短路模型,我们读入时候倒着建边,从 n n n 点开始跑一遍单源最短路就是答案!时间复杂度 O ( n 3 ) O(n^3) O(n3) ,空间复杂度 O ( n + m ) O(n+m) O(n+m)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define maxn 100005
#define INF 0x3f3f3f3f
typedef long long ll;

using namespace std;

struct Edge
{
    int to, next;
}edge[maxn*2];
int head[maxn], tot, dis[maxn];
bool vis[maxn];
void Init()
{
    memset(head, -1, sizeof(head));
    memset(dis, INF, sizeof(dis));
    memset(vis, false, sizeof(vis));
    tot = 0;
}
void add(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
void spfa(int s)
{
    queue<int> que;
    while(!que.empty()) que.pop();
    que.push(s); vis[s] = true;
    dis[s] = 0;
    while(!que.empty()){
        int tmp = que.front();
        que.pop(); vis[tmp] = false;
        for (int i = head[tmp]; ~i; i = edge[i].next){
            int to = edge[i].to;
            if (dis[to] > dis[tmp] + 1){
                dis[to] = dis[tmp] + 1;
                if (!vis[to]){
                    que.push(to); vis[to] = true;
                }
            }
        }
    }
}
int main()
{
    int T, n, x;

    scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        Init();
        for (int i = 1; i <= n; i++){
            scanf("%d", &x);
            if (i - x >= 1) add(i - x, i);
            if (i + x <= n) add(i + x, i);
        }
        spfa(n);
        for (int i = 1; i < n; i++){
            if (dis[i] == INF) printf("-1 ");
            else printf("%d ", dis[i]);
        }
        printf("0\n");
    }
    return 0;
}

D - zhb love chess

出题人: c w h b b t cwhbbt cwhbbt

通过/提交: 1 / 19 1/19 1/19

题解: 考虑动态规划 f ( n o w , i , j ) f(now,i,j) f(now,i,j) 当前先手是 n o w now now 位置在 ( i , j ) (i,j) (i,j) 走到 ( 2 , m ) (2,m) (2,m) 的分数,根据他们两个人的游戏策略有:

  • f ( z h b , i , j ) = a i , j + m a x { f ( z y , i − 1 , j ) , f ( z y , i , j + 1 ) } f(zhb,i,j)=a_{i,j}+max\{f(zy,i-1,j),f(zy,i,j+1)\} f(zhb,i,j)=ai,j+max{f(zy,i1,j),f(zy,i,j+1)}
  • f ( z y , i , j ) = a i , j + m i n { f ( z h b , i − 1 , j ) , f ( z h b , i , j + 1 ) } f(zy,i,j)=a_{i,j}+min\{f(zhb,i-1,j),f(zhb,i,j+1)\} f(zy,i,j)=ai,j+min{f(zhb,i1,j),f(zhb,i,j+1)}

这个转移有些奇怪,不好直接递推,但是因为行数只有 2 2 2 ,不妨直接讨论当前位于第 i i i 列第 1 1 1 行和第 2 2 2 行,如果当前位于 ( 1 , i ) (1,i) (1,i) 那么它只可能走向 ( 2 , i ) (2,i) (2,i) ( 1 , i + 1 ) (1,i+1) (1,i+1) ,而 ( 2 , i ) (2,i) (2,i) 下一步只能走到 ( 2 , i + 1 ) (2,i+1) (2,i+1) 也就是 f ( n o w , 1 , i ) f(now,1,i) f(now,1,i) 可以转化为从 f ( n o w , 1 , i + 1 ) f(now,1,i+1) f(now,1,i+1) f ( n o w , 2 , i + 1 ) f(now,2,i+1) f(now,2,i+1) 转移,同理 f ( n o w , 2 , i ) f(now,2,i) f(now,2,i) 也可以转化为从 f ( n o w , 1 , i + 1 ) f(now,1,i+1) f(now,1,i+1) f ( n o w , 2 , i + 1 ) f(now,2,i+1) f(now,2,i+1) 转移过来。因此最后从 m m m 1 1 1 倒着 D P DP DP f ( z y , 1 , 1 ) f(zy,1,1) f(zy,1,1) 就是答案,注意边界情况。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int a[2][maxn];
ll f[2][2][maxn];
int main() {
    int m;
    scanf("%d", &m);
    for (int i = 0; i <= 1; i++) {
        for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]);
    }
    f[0][0][m] = f[1][0][m] = a[0][m] + a[1][m];
    f[0][1][m] = f[1][1][m] = a[1][m];
    for (int i = m - 1; i >= 1; i--) {
        f[0][0][i] = a[0][i] + max(f[1][0][i + 1], f[0][1][i + 1] + a[1][i]);
        f[1][0][i] = a[0][i] + min(f[0][0][i + 1], f[1][1][i + 1] + a[1][i]);
        f[0][1][i] = a[1][i] + max(f[0][1][i + 1], a[0][i] + f[0][0][i + 1]);
        f[1][1][i] = a[1][i] + min(f[0][1][i + 1], a[0][i] + f[1][0][i + 1]);
    }
    printf("%lld\n", f[1][0][1]);
    return 0;
}

E - zy love banner

出题人: s a d y i 98 sadyi98 sadyi98

通过/提交: 43 / 68 43/68 43/68

题解: 读入字符串以后 O ( n ) O(n) O(n) 扫一遍,维护连续zy字串的长度最大值就是答案。

#include <bits/stdc++.h>
using namespace std;
char s[2005];
int main() {
    scanf("%s", s + 1);
    int mx = 0, cnt = 0;
    int len = strlen(s + 1);
    for (int i = 1; i <= len; i++) {
        if (s[i] == 'z' && s[i + 1] == 'y') {
           cnt++;
           i++;
        } else cnt = 0;
        mx = max(mx, cnt);
    }
    printf("%d\n", mx);
    return 0;
}

F - 新食堂

出题人: s a d y i 98 sadyi98 sadyi98

通过/提交: 28 / 86 28/86 28/86

题解: 通过分析从 n = 5 n=5 n=5 时到 n = 6 n=6 n=6 时可以发现,当每两个人之间恰好相隔一个座位时,桌子长度一直增加直到每个人之间相隔两个时都无法坐下下一个人,而之后只要一个一个的增加长度就可以增加能坐下的人数,于是我们只用分析恰好只间隔一个座位就能坐下的人数就行了。也就是分析数列 2 , 3 , 5 , 9 , 17...... 2,3,5,9,17...... 2,3,5,9,17...... 可以发现,他们之间的差值是以 2 2 2 为底的幂,而且可以根据之前的分析可以确定这个数列就是这样增加的。所以整理之后可以得到: l = a i + ( a i − 1 ) ∗ 2 + n − a i l=a_i+(a_i-1)*2+n-a_i l=ai+(ai1)2+nai 其中 a i a_i ai 是数列中小于 n n n 的最大项。

#include <stdio.h>
int main()
{
	int n;
	int num[5]={0,1,3,5};
	while(scanf("%d",&n)!=EOF)
	{
		if(n<=3)
		{
			printf("%d\n",num[n]);
			continue;
		}
		int base=3,temp=2;
		while(base+temp<n)
		{
			base+=temp;
			temp*=2;
		}
		printf("%d\n",base*2-2+n);
	}
}

G - 一道平衡树好题

出题人: z h b e r zhber zhber

通过/提交: 5 / 29 5/29 5/29

题解: 首先得意识到,每插入一个数,函数执行的次数就是在这颗平衡树上经过的边数 + 1 +1 +1 ,平衡树上的一条边,就对应于一个区间,例如节点 b i b_i bi ,假设它的直接左儿子是 a i   ( a i &lt; b i ) a_i\ (a_i&lt; b_i) ai (ai<bi) ,那么当你询问往平衡树插入一个节点要经过多少条边的时候,实际上就是询问目前有多少个包含了它的区间。首先我们把序列做离散化,此时 1 ≤ a i ≤ n 1\leq a_i\leq n 1ain

对于一个树上的点 k k k,只有某些数字插入时会经过它。假设根节点数字是 p p p,根节点左儿子数字是 q q q 。如果插入时经过根节点, x x x 的值一定属于 [ 1 , n ] [1,n] [1,n] ;如果经过根节点的左儿子, x x x 的值属于 [ 1 , p − 1 ] [1,p-1] [1,p1];如果经过了 ”根节点左儿子” 的右儿子, x x x 的值属于 [ q + 1 , p − 1 ] [q+1,p-1] [q+1,p1]……因此每个点对应一个区间,如果新加入的数字属于这个区间就会经过它。插入一个数的时候,执行次数实际上是 “当前包含这个点的区间个数” + 1 +1 +1 + 1 +1 +1 是访问空子树并新建节点。同时,每加入一个点也会增加一个它对应的区间。这是一个简单的单点查询、区间修改的问题,树状数组或线段树都可以胜任。至于计算这个新点对应的区间,这个值是当前它的前驱当前它的后继 [ [ [ 当前它的前驱 + 1 , +1, +1, 当前它的后继 − 1 ] -1] 1](只有属于这个区间的值才会和新点走一样的路径到这里),用 set 维护即可,可以设置两个哨兵 0 0 0 n + 1 n+1 n+1 防止越界。

另外,在验题时候还有神仙发现另一种做法——把当前数的前驱和后继的插入执行次数取个max加一就是答案,大家可以试一试。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( n ) O(n) O(n)

树状数组版本:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define mx 200010
ll c[mx],ans;
void add(int x,int d){for (int i=x;i<mx;i+=i&(-i))c[i]+=d;}
ll ask(int x){ll sum=0;for (int i=x;i;i-=i&(-i))sum+=c[i];return sum;}
int n,q,cnt,a[mx];
map<int,int>mp;
set<int>s;
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)scanf("%d",a+i),mp[a[i]]=1;
    for (auto i=mp.begin();i!=mp.end();i++)i->second=++cnt;
    for (int i=1;i<=n;i++)a[i]=mp[a[i]];
    s.insert(0);s.insert(n+1);
    for (int i=1;i<=n;i++)
    {
        auto succ=s.lower_bound(a[i]);
        auto pred=succ;pred--;
        int l=(*pred)+1,r=(*succ)-1;
        add(l,1);add(r+1,-1);
        ans+=ask(a[i]);
        s.insert(a[i]);
    }
    printf("%lld\n",ans);
    return 0;
}

线段树版本:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
struct SegmentTree {
#define ls (rt<<1)
#define rs (rt<<1|1)
    struct Tree {
        int l, r, tag, s;
        int len() { return r - l + 1; }
    } t[maxn << 2];

    void push_up(int rt) {
        t[rt].s = t[ls].s + t[rs].s;
    }

    void push_down(int rt) {
        if (t[rt].tag) {
            if (t[ls].l != t[ls].r) t[ls].tag += t[rt].tag;
            if (t[rs].l != t[rs].r) t[rs].tag += t[rt].tag;
            t[ls].s += t[ls].len() * t[rt].tag;
            t[rs].s += t[rs].len() * t[rt].tag;
            t[rt].tag = 0;
        }
    }

    void build(int l, int r, int rt) {
        t[rt].l = l, t[rt].r = r, t[rt].tag = 0;
        if (l == r) {
            t[rt].s = 0;
            return;
        }
        int m = (l + r) / 2;
        build(l, m, ls);
        build(m+1, r, rs);
    }

    void update(int l, int r, int rt) {
        if (l <= t[rt].l && t[rt].r <= r) {
            t[rt].s += t[rt].len();
            t[rt].tag++;
            return;
        }
        push_down(rt);
        int m = (t[rt].l + t[rt].r) / 2;
        if (l <= m) update(l, r, ls);
        if (r > m) update(l, r, rs);
        push_up(rt);
    }

    int query(int p, int rt) {
        if (t[rt].l == t[rt].r) return t[rt].s;
        push_down(rt);
        int m = (t[rt].l + t[rt].r) / 2;
        if (p <= m) return query(p, ls);
        return query(p, rs);
    }
} T;
set<int> s;
int a[maxn], b[maxn];
int main() {
    int n;
    scanf("%d", &n);
    int up = 0, cnt = 0;
    b[++cnt] = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b[++cnt] = a[i];
        up = max(up, a[i]);
    }
    b[++cnt] = up + 1;
    sort(b + 1, b + cnt + 1);
    T.build(1, cnt, 1);
    s.insert(0), s.insert(up + 1);
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        int p = lower_bound(b + 1, b + cnt + 1, a[i]) - b;
        s.insert(a[i]);
        auto it = s.find(a[i]);
        int pre = *(--it);
        ++it;
        int suf = *(++it);
        int l = lower_bound(b + 1, b + cnt + 1, pre) - b;
        int r = lower_bound(b + 1, b + cnt + 1, suf) - b;
        T.update(l, p - 1, 1);
        T.update(p, r, 1);
        ans += T.query(p, 1);
    }
    printf("%lld\n", ans);
    return 0;
}

H - 曜神拯救世界

出题人: k o n n y a k u konnyaku konnyaku

通过/提交: 0 / 4 0/4 0/4

题解: 首先将 n n n 种攻击按神器 A A A 需要的耐久降序排序,接下来枚举把第一件神器的耐久加到多少。当第一件神器的耐久恰能抵挡第 i i i 种攻击时,只需考虑前 i − 1 i-1 i1 种攻击对神器 B B B 和神器 C C C 的影响。注意 1 ≤ a i , b i , c i ≤ 1 0 6 1\leq a_i,b_i,c_i\leq10^6 1ai,bi,ci106 ,所以可再维护一个数组 B [ j ] B[j] B[j] 代表当前神器 B B B 的耐久度为 j j j 时神器 C C C 的耐久度最少为多少,此时答案则为 a i + m i n { B [ j ] + j } a_i+min\{B[j]+j\} ai+min{B[j]+j}。接下来考虑当神器 A A A 的耐久度从 a i a_i ai 变为 a i + 1 a_{i+1} ai+1 时对 B B B 数组的影响:对所有 0 ≤ j &lt; b i , B [ j ] = m a x { B [ j ] , c i } 0\leq j&lt; b_i,B[j]=max\{B[j],c_i \} 0j<bi,B[j]=max{B[j],ci},该操作可用线段树区间操作来 O ( l o g n ) O(logn) O(logn) 实现;还有 m i n { B [ j ] + j } min\{B[j]+j\} min{B[j]+j} 的计算也可以用线段树来维护区间最小值,因此总的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

具体实现:显然数组 B [ j ] B[j] B[j] 是随着 j j j 的增加单调递减的,所以上文中提到的 B [ j ] = m a x { B [ j ] , c i } B[j]=max\{B[j],c_i \} B[j]=max{B[j],ci} 操作可变成找到一点 k ( 0 ≤ k ≤ b i ) k(0\leq k\leq b_i) k(0kbi) ,对所有 $k\leq j< b_i,B[j]=c_i $ ,这就成了裸的线段树区间操作;可用线段树维护该节点中 B B B 数组的最大值最小值以及 B [ j ] + j B[j]+j B[j]+j 的最小值,当该节点的 m a x B ≥ c i maxB\geq c_i maxBci 时则可以直接return不用做任何修改;当该节点的 m i n B ≤ c i minB\leq c_i minBci 时则可以将该节点的 m i n B , m a x B minB,maxB minB,maxB 均改为 c i c_i ci m i n ( B [ j ] + j ) min(B[j]+j) min(B[j]+j) 改为该区间左端点的下标加上 c i c_i ci ,同时用 t a g tag tag 标记该节点;否则就继续往该节点的两个叶子节点进行递归修改。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
/* head */
struct SegmentTree {
#define ls (rt<<1)
#define rs (rt<<1|1)
    struct Tree {
        int l, r, mi, mx, res, tag;
    } t[maxn << 2];

    void push_up(int rt) {
        t[rt].mi = min(t[ls].mi, t[rs].mi);
        t[rt].mx = max(t[ls].mx, t[rs].mx);
        t[rt].res = min(t[ls].res, t[rs].res);
    }

    void push_down(int rt) {
        if (t[rt].tag) {
            if (t[ls].l != t[ls].r) t[ls].tag = max(t[ls].tag, t[rt].tag);
            if (t[rs].l != t[rs].r) t[rs].tag = max(t[rs].tag, t[rt].tag);
            t[ls].mi = t[ls].mx = t[rt].tag;
            t[rs].mi = t[rs].mx = t[rt].tag;
            t[ls].res = t[ls].mi + t[ls].l;
            t[rs].res = t[rs].mi + t[rs].l;
            t[rt].tag = 0;
        }
    }

    void build(int l, int r, int rt) {
        t[rt].l = l, t[rt].r = r, t[rt].tag = 0;
        if (l == r) {
            t[rt].mi = t[rt].mx = 0;
            t[rt].res = l;
            return;
        }
        int m = (l + r) / 2;
        build(l, m, ls);
        build(m+1, r, rs);
        push_up(rt);
    }

    void update(int l, int r, int c, int rt) {
        if (t[rt].mi >= c) return;
        if (l <= t[rt].l && t[rt].r <= r && t[rt].mx <= c) {
            t[rt].mi = t[rt].mx = c;
            t[rt].res = c + t[rt].l;
            t[rt].tag = max(t[rt].tag, c);
            return;
        }
        push_down(rt);
        int m = (t[rt].l + t[rt].r) / 2;
        if (l <= m) update(l, r, c, ls);
        if (r > m) update(l, r, c, rs);
        push_up(rt);
    }
} T;
struct node {
    int a, b, c;
    friend bool operator< (const node& x, const node& y) {
        return x.a > y.a;
    }
} p[maxn];
void solve() {
    int n;
    scanf("%d", &n);
    int up = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].c);
        up = max(up, p[i].b);
    }
    sort(p + 1, p + n + 1);
    T.build(0, up, 1);
    int ans = 1e9;
    for (int i = 1; i <= n; i++) {
        ans = min(ans, p[i].a + T.t[1].res);
        T.update(0, p[i].b - 1, p[i].c, 1);
    }
    ans = min(ans, T.t[1].res);
    printf("%d\n", ans);
}
int main() {
 	int t;
  	scanf("%d", &t);
  	while (t--) solve();
	return 0;
}

附: 本题被验题人周神仙 z h b e r zhber zhberstd::set更简短优美地实现了,大家可以尝试一下。

I - 数字集合

出题人: s a i r y o sairyo sairyo

通过/提交: 26 / 153 26/153 26/153

题解: 分两种情况考虑,若 a a a b b b 中有 1 1 1 个为 0 0 0 ,则集合中最后只存在 a a a b b b 两个数。若 a a a b b b 都不为 0 0 0 。考虑集合中如果可以存在 1 1 1 和任意其他正整数则所有非零正整数都能通过 + 1 +1 +1 − 1 -1 1 被造出。首先可以通过辗转相减使 g c d ( a , b ) gcd(a,b) gcd(a,b) 其存在于集合中。设 a = k 1 × g c d ( a , b ) , b = k 2 × g c d ( a , b ) a=k_1×gcd(a,b),b=k_2×gcd(a,b) a=k1×gcd(a,b),b=k2×gcd(a,b) ,则可使 k 1 k_1 k1 k 2 k_2 k2 加入集合。则可使 g c d ( k 1 , k 2 ) gcd(k_1,k_2) gcd(k1,k2) 加入集合,由于已知 g c d ( k 1 , k 2 ) = 1 gcd(k_1,k_2)=1 gcd(k1,k2)=1 ,因此可使 1 1 1 加入集合。则所有非零数都可以被造出。

#include <cstdio>
int main() {
	int a,b,c;
	scanf("%d%d%d",&a,&b,&c);
	if(a==0||b==0){
		if(c==a||c==b) printf("YES\n");
		else printf("NO\n");
	} else {
		if(c!=0) printf("YES\n");
		else printf("NO\n");
	}
    return 0;
}

J - 不过都一个套路

出题人: m i a m i a o miamiao miamiao

通过/提交: 3 / 28 3/28 3/28

题解: 直接正面求复杂度是不可接受的。而区间 [ 2 , k ] [2,k] [2,k] 所有数的总和是 ( 2 + k ) × ( k − 1 ) 2 \frac{(2+k)×(k-1)}{2} 2(2+k)×(k1) ,答案可以通过总和减去那些与 n n n 不互素的数求得。而对于一个数 i i i 如果它与 n n n 不互素,那么它的倍数 a ⋅ i a·i ai 也与 n n n 不互素,而且构成以 i i i 为首项 i i i 为公差的等差数列。而发现 ∏ i = 11 18 i &gt; 1 0 9 \prod\limits_{i=11}^{18}i&gt;10^9 i=1118i>109 ,所以 n n n 的因数不会特别多,我们可以对 n n n 进行因数分解,枚举它的因子(可以通过状压或者爆搜实现) O ( 1 ) O(1) O(1) 地算出小于 k k k i i i 为因子的数的总和,但是我们会把不互素的因子 i i i j j j 把它们的倍数多算(比如算 2 2 2 的时候把 6 6 6 算上去了, 同理在 3 3 3 的时候也把 6 6 6 算上去了)因此还得把因子容斥一下才是最终答案。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
/* head */
vector<int> fac;
inline void solve() {
    int n, k;
    scanf("%d%d", &n, &k);
    fac.clear();
    for (int i = 2; i * i <= n; i++) {
        if (n > 0 && n % i == 0) {
            fac.push_back(i);
            while (n && n % i == 0) n /= i;
        }
    }
    if (n > 1) fac.push_back(n);
    ll sum = 1LL * (2 + k) * (k - 1) / 2;
    ll res = 0;
    int sz = fac.size();
    for (int i = 1; i < (1 << sz); i++) {
        ll tmp = 1;
        int flag = 0;
        for (int j = 0; j < sz; j++) {
            if (i & (1 << j)) {
                flag++;
                tmp *= fac[j];
            }
        }
        int cnt = k / tmp;
        if (flag & 1) res += tmp * cnt * (cnt + 1) / 2;
        else res -= tmp * cnt * (cnt + 1) / 2;
    }
    printf("%lld\n", sum - res);
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) solve();
    return 0;
}

K - 踩人

出题人: z h b e r zhber zhber

通过/提交: 5 / 18 5/18 5/18

题解: 简化一下题意就是多次询问无向图中 “点权前 k k k大的点” 的诱导子图中 a , b a,b a,b 两点是否联通。诱导子图就是选一个点集合 V ′ V&#x27; V ,从原图中取出 V ′ V&#x27; V 中的点,以及 “边两端点都在 V ′ V&#x27; V 内” 的边,构成的一个新图。

不难想到把所有的询问读下来,然后按照 k k k 从小到大排序离线做。那么只要每次加一个点进来,并查集维护图的信息,使得图的状态从 “点权前 k k k 大” 变成 “点权前 k + 1 k+1 k+1 大” ,就能够处理连通性询问了。那么问题的关键是怎么找出新加入一个点后,所有需要加入的新边。注意到一条边需要被加入图中时,一定是它的两端点都是点权前 k k k 大了。假设一条边 E E E 的两端点分别是前 a a a 大和前 b b b 大, E E E 会被加入图中当且仅当现在处理到点权前 m a x { a , b } max\{a,b\} max{a,b} 大。那么不妨定义一个边的点权为 “两端点的点权排名更大的那个” ,这样只要当前的 k k k 等于边权,即可加入这条边。排序尺取即可。时间复杂度 O ( n l o g n + m l o g m + t l o g t ) O(nlogn+mlogm+tlogt) O(nlogn+mlogm+tlogt) ,空间复杂度 O ( n + m + t ) O(n+m+t) O(n+m+t)

#include<bits/stdc++.h>
using namespace std;
struct query{int k,a,b,id,ans;}q[100010];
bool cmp1(const query &a,const query &b){return a.k<b.k;}
bool cmp2(const query &a,const query &b){return a.id<b.id;}
struct edge{int x,y,z;}e[100010];
bool operator<(const edge &a,const edge &b){return a.z<b.z;}
struct point{int v,id;}p[100010];
bool operator<(const point &a,const point &b){return a.v>b.v;}
int fa[100010];inline int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[x]);}
int n,m,t;
int rnk[100010];
int main()
{
    scanf("%d%d%d",&n,&m,&t);
    for (int i=1;i<=n;i++)scanf("%d",&p[i].v),p[i].id=i;
    sort(p+1,p+n+1);
    for (int i=1;i<=n;i++)rnk[p[i].id]=i;
    for (int i=1;i<=m;i++)scanf("%d%d",&e[i].x,&e[i].y),e[i].z=max(rnk[e[i].x],rnk[e[i].y]);
    sort(e+1,e+m+1);
    for (int i=1;i<=t;i++)scanf("%d%d%d",&q[i].k,&q[i].a,&q[i].b),q[i].id=i;
    sort(q+1,q+t+1,cmp1);
    for (int i=1;i<=n;i++)fa[i]=i;
    int nm=1,nt=1;
    for (int i=1;i<=n;i++)
    {
        while (nm<=m&&e[nm].z==i)
        {
            int fx=getfa(e[nm].x),fy=getfa(e[nm].y);
            nm++;
            if (fx==fy)continue;
            fa[fy]=fx;
        }
        while (nt<=t&&q[nt].k==i)
        {
            if (q[nt].a==q[nt].b)q[nt].ans=1,nt++;else
            q[nt].ans=(getfa(q[nt].a)==getfa(q[nt].b)),nt++;
        }
    }
    sort(q+1,q+t+1,cmp2);
    for (int i=1;i<=t;i++)printf("%s\n",q[i].ans?"7777777":"2333333");
    return 0;
}

L - Unsuccessful hack

出题人: z h b e r zhber zhber

通过/提交: 4 / 12 4/12 4/12

题解: 注意到,对于所有长度 1...4 1...4 1...4 的小写字母字符串(共 26 + 2 6 2 + 2 6 3 + 2 6 4 = 475254 26+26^2+26^3+26^4=475254 26+262+263+264=475254 个),它们的hash值已经包括了 [ 0 , 9982 ] [0,9982] [0,9982] 一共 9983 9983 9983 个数,是所有hash的可能结果了。因此直接暴力对每个hash值记两个对应的串输出即可。记两个是为了防止输出和输入串一样被判为错,此时需要用另一个替换。

另解: 注意到这个hash有这样性质,假设 A A A 是一个长度是 3 3 3 的倍数的字符串, B B B 是任意字符串,如果 h a s h ( A ) = 0 hash(A)=0 hash(A)=0 h a s h ( B ) = k hash(B)=k hash(B)=k ,那么 h a s h ( A + B ) = k hash(A+B)=k hash(A+B)=k 。这里 + + + 号代表的是字符串的拼接。本地暴力打表即可发现长度是 3 3 3 的hash值为0的串就有两个,分别是 z t q ztq ztq s x j sxj sxj 。只要在读入串之前加上它们之一然后原封不动地输出即可。

时间复杂度 O ( T ⋅ l e n ) O(T·len) O(Tlen) ,空间复杂度 O ( l e n ) O(len) O(len)

另外,在比赛中一位神仙发现了在原串后面加入444个同样字母来使得hash相等的做法,出题人并不会证明QAQ

拓展: 在取模数很大的时候,可能没有3位的hash是 0 0 0 的字符串,我们可以通过双向广搜找6位的字符串,倒着找的时候需要使用模意义下乘法逆元。取模数可以设定为非质数,那么改为使用中国剩余定理求逆或者拓展欧几里得法求逆即可。

#include<bits/stdc++.h>
using namespace std;
char s[1010];
int main()
{
    int T;
  	scanf("%d",&T);
    for (int i=1;i<=T;i++)
    {
        scanf("%s",s+1);
        printf("sxj%s\n",s+1);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值