2021acm-icpc区域赛(南京)补题笔记

3 篇文章 0 订阅

前言

2021南京,号称今年最卷的icpc区域赛。

上周末训练赛和队友打了一下,争取多补几道题。

比赛链接:https://codeforces.com/gym/103470

题目一览

签到题:A,C,M,H

铜牌题:D

银牌题:J

金牌题:I ,G

我不能做的题:B,F,K,L

本场差不多是4.5题铜,5.5题银,7题金(4题铜5题银的概率不大,约等于5铜,6银,7金)

A.Oops, It’s Yesterday Twice More(签到)

题意

不知道,队友写的

思路

不知道,队友写的

M. Windblume Festival(签到)

题意

给一个长度为n整数序列 a [ ] a[] a[] , 每次操作可以选择两个下标 i , j i,j ij ( i < j i < j i<j,环形) , 移除 a j a_j aj , 并且令 a i = a i − a j a_i = a_i - a_j ai=aiaj

最后会剩下一个元素,要求最大化这个元素。 ( 1 < = n < = 1 0 6 1<=n<=10^6 1<=n<=106)

input

5
4
1 -3 2 -4
11
91 66 73 71 32 83 72 79 84 33 93
12
91 66 73 71 32 83 72 79 84 33 33 93
13
91 66 73 71 32 83 72 79 84 33 33 33 93
1
0

output

10
713
746
779
0

Note

For the first sample test case follow the strategy shown below, where the underlined integers are the integers held by the players selected in each turn.

{1–,−3,2,−4}(select x=4) →→ {−3,2,−5} (select x=2x) →→ {−3,7} (select x=2) →→ {10}}.
思路

这是一个环形的结构,所以我们可以“自行地”决定一个数的贡献是正的还是负的。并且一定会有一个正的贡献,并且n>1时必定有一个负的贡献。

所以,答案就是 a n s = m a x − m i n + s u m ( r e s ) ans = max - min + sum(res) ans=maxmin+sum(res)

C. Klee in Solitary Confinement(思维+前缀和)

题意

给一个长度为n整数序列 a [ ] a[] a[] , 给一个正整数 k k k

可以选择一段区间 [ l , r ] [l,r] [l,r] , 将其中所以元素加 k k k

要求最大化出现次数最多的数的数量。

( 1 ≤ n ≤ 1 0 6 , − 1 0 6 ≤ k ≤ 1 0 6 , − 1 0 6 ≤ a i ≤ 1 0 6 ) (1≤n≤10^6, −10^6≤k≤10^6 ,−10^6≤a_i≤10^6) (1n106,106k106,106ai106

Examples

input

5 2
2 2 4 4 4

output

5

input

7 1
3 2 3 2 2 2 3

output

6

input

7 1
2 3 2 3 2 3 3

output

5

input

9 -100
-1 -2 1 2 -1 -2 1 -2 1

output

3
思路

不难发现,对于每个数字 i i i,我们只需要考虑 i i i i + k i+k i+k 的相对位置关系,可以忽略其他所有的数字。

所以我们可以遍历一遍数组,用 v e c t o r vector vector将每个 i i i i + k i+k i+k分在一个组里并保留相对位置。

对每个组,令 a i = 1 a_i = 1 ai=1 , a i + k = − 1 a_i+_k = -1 ai+k=1,目标就是最大化区间找出区间和最大的区间 [ l , r ] [l,r] [l,r]

意思就是对于区间 [ l , r ] [l,r] [l,r] , 我们能令最多的 a i a_i ai变成 a i + k a_i+_k ai+k 的同时,令最少的 a i + k a_i+_k ai+k被改变。

实现上,我们维护一个前缀和 sum[i] 和最小前缀和 sum_min[i] 即可。

代码
#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
#define int long long
const int BASE=1e6;
const int MAXN=4e6+20;
int n,k,a,sum[MAXN/4],minn,maxn,summin[MAXN/4],cnt[MAXN],flag,ans,tot;
vector<int> s[MAXN];

void solve(int num) {
    tot=s[num].size();
    if(tot==0) return;
    ans=max(ans,cnt[num]+s[num][0]);
    sum[1]=s[num][0];
    summin[1]=min(sum[0],sum[1]);
    for(int i=2;i<=tot;++i) {
        sum[i]=sum[i-1]+s[num][i-1];
        summin[i]=min(summin[i-1],sum[i]);
        ans=max(ans,sum[i]-summin[i-1]+cnt[num]);
        //cout<<i<<" "<<sum[i]<<" "<<summin[i-1]<<'\n';
    }

    for(int i=1;i<=tot;++i) sum[i]=summin[i]=0;
}

signed main()
{
    scanf("%lld%lld",&n,&k);
    flag=1;
    if(k<0) flag=-1,k=-k;
    minn=5e6,maxn=-1;
    for(int i=1;i<=n;++i) {
        scanf("%lld",&a);
        a=a*flag+BASE;
        minn=min(minn,a);
        maxn=max(maxn,a);
        ++cnt[a];
        ans=max(ans,cnt[a]);
        s[a].push_back(-1);
        s[a+k].push_back(1);
    }
    if(k!=0)
    {
        for(int i=minn+k;i<=maxn;++i)
            solve(i);
    }
    cout<<ans;
    return 0;
} 

H. Crystalfly(树形dp)

题意

给一棵 n n n 个点带点权的树 ( 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105 , 1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109)

从 1 号点出发,每个点的点权可以吃一次,要求最大化吃到的点权和。

每个点还有另一个值 t i t_i ti , 当你走到这个点的相邻节点时,这个点会被" d i s t u r b disturb disturb " , t i t_i ti 开始倒计时,如果在倒计时结束前还没能吃到这个点,那这个点的点权会消失(注意,可以经过,只是吃不到点权)。 其中 1 < = t i < = 3 1<=t_i<=3 1<=ti<=3

输入多组样例,每组样例第一行 n n n,第二行点权,第三行 t i t_i ti,然后是树边

input

2
5
1 10 100 1000 10000
1 2 1 1 1
1 2
1 3
2 4
2 5
5
1 10 100 1000 10000
1 3 1 1 1
1 2
1 3
2 4
2 5

output

10101
10111

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ACto4wLB-1649091209048)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220404114835027.png)]

思路

1 < = t i < = 3 1<=t_i<=3 1<=ti<=3是一个很关键的信息。

t i = 1 或 2 t_i = 1或2 ti=12不影响,对于每个父节点我们只能选一个孩子,该吃不到的点还是吃不到。

t i = 3 t_i = 3 ti=3 时 , 我们可以考虑先走一个孩子节点,再回到父节点,再走另外一个孩子节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3UADrZbn-1649091209050)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220404115659675.png)]

如图,当 2号点有 t i = 3 t_i = 3 ti=3 时,我们可以先绕到3号点,再走2号。

这样做,我们可以同时吃到1号的两个孩子2号和3号,

但要注意,3号的孩子5号和6号(橙色框)必定吃不到点权。

所以,当我们考虑“绕”这种策略时,要考虑到孙子节点;而不考虑“绕”这种策略时,孙子节点是不需要考虑的,

这影响到了我们dp方程的设置。

考虑 d p [ i ] dp[i] dp[i] 代表 i 的子树的最优解,不包括 i i i

“不包括 i i i ”,那么不考虑“绕”这种策略,我们就可以直接将子节点的dp值直接合并到父节点。

所以,不考虑“绕”的策略为 : d p [ i ] = ∑ v = 1 m d p [ v ] + m a x ( a [ v ] ) dp[i] = \sum_{v=1}^m dp[v] + max(a[v]) dp[i]=v=1mdp[v]+max(a[v])

考虑“绕”这种策略时,假设要绕的点是 w w w w w w的孩子是 w s w_s ws 。 那么就是 减掉 d p [ w ] dp[w] dp[w], 加上 ∑ w s = 1 m d p [ w s ] \sum_{w_s=1}^m dp[w_s] ws=1mdp[ws] ,再加上 a [ w ] a[w] a[w]

d p [ i ] = m a x ( d p [ i ] , ∑ v = 1 m d p [ v ] + a [ v ] − d p [ w ] + ∑ w s = 1 m d p [ w s ] + a [ w ] dp[i] = max(dp[i] , \sum_{v=1}^m dp[v] + a[v] - dp[w] +\sum_{w_s=1}^m dp[w_s]+a[w] dp[i]=max(dp[i],v=1mdp[v]+a[v]dp[w]+ws=1mdp[ws]+a[w]) .

(其中, v v v必须保证 t v = 3 t_v = 3 tv=3)

我们发现这样处理的话每次不仅得遍历儿子还得遍历孙子,因此考虑将孙子的dp值存起来

s u m [ u ] = ∑ v = 1 m d p [ v ] sum[u] = \sum_{v=1}^m dp[v] sum[u]=v=1mdp[v],

那么两种策略分别为:

  1. d p [ i ] = s u m [ i ] + m a x ( a [ v ] ) dp[i] = sum[i]+max(a[v]) dp[i]=sum[i]+max(a[v])
  2. d p [ i ] = m a x ( d p [ i ] , s u m [ i ] + a [ v ] + m a x ( − d p [ w ] + s u m [ w ] + a [ w ] ) ) dp[i] = max(dp[i],sum[i]+a[v] + max(-dp[w]+sum[w]+a[w])) dp[i]=max(dp[i],sum[i]+a[v]+max(dp[w]+sum[w]+a[w]))

策略2中的走法是: i i i 先到 w w w w w w 回到 i i i i i i 再到 v v v

由转移方程可以看出 , − d p [ w ] + s u m [ w ] + a [ w ] -dp[w]+sum[w]+a[w] dp[w]+sum[w]+a[w] 应该取最大的 , v v v 必须满足 t v = 3 t_v = 3 tv=3

yysy ,这题作为最后一道签到题来讲思路上还是挺绕的,但场上确实过了那么多人…

如果在不太卷的赛站这大概会是铜牌题?

代码

思路挺绕,但写起来不麻烦

#include<bits/stdc++.h>
using namespace std;
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define int long long
const int N = 1e6+100;
vector<int> edge[N];
int a[N],ti[N];
int n;
int dp[N],sum[N];
void dfs(int u,int f){
    int max_av = 0;
    for(auto v:edge[u]){
        if(v==f) continue;
        dfs(v,u);
        sum[u] += dp[v];
        max_av = max(max_av,a[v]);
    }
    dp[u] = sum[u] + max_av;
    if(edge[u].size()<=1) return;
    
    // 预处理出-dp[w]+sum[w]+a[w] 最大的两项 , 因为w和v可能重复
    // mx[0] 最大的 -dp[w]+sum[w]+a[w]
    // mx[1] 第二大的 -dp[w]+sum[w]+a[w]
    pair<int,int> mx[2];
    for(auto w:edge[u]){
        if(w==f) continue;
        int now = -dp[w]+sum[w]+a[w];
        if(now>mx[0].first){
            swap(mx[0],mx[1]);
            mx[0] = make_pair(now,w);
        }
        else if(now>mx[1].first){
            mx[1] = make_pair(now,w);
        }
    }
    for(auto v:edge[u]){
        if(v==f) continue;
        if(ti[v]==3){
            if(v!=mx[0].second)
                dp[u]= max(dp[u],sum[u]+a[v]+mx[0].first);
            else
                dp[u] = max(dp[u],sum[u]+a[v]+mx[1].first);
        }
    }
}
signed main(){
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        rep(i,1,n) cin>>a[i],edge[i].clear();
        rep(i,1,n) cin>>ti[i];
        rep(i,1,n-1){
            int x,y;
            cin>>x>>y;
            edge[x].push_back(y);
            edge[y].push_back(x);
        }
        rep(i,1,n){
            dp[i] = sum[i] = 0;
        }
        dfs(1,0);
        cout<<dp[1]+a[1]<<endl;
    }
}

D.Paimon Sorting(思维+数据结构)

题意

派蒙(没错就是派蒙)发明了一种排序方法,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HK97Crxy-1649091209050)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220404222346528.png)]

给你一个长度为 n n n 的正整数序列 a [ ] a[] a[] , 问 :对于 a [ ] a[] a[] 的每个前缀,执行上述的排序方法,各需要排序几次?

( 1 < = n < = 1 0 5 , 1 < = a i < = n 1<=n<=10^5,1<=a_i<=n 1<=n<=105,1<=ai<=n , 多组样例保证 n n n 不大于 1 0 6 10^6 106)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rWJJMipV-1649091209051)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220404222602040.png)]

思路

首先派蒙的排序方法很简单,可以抄下来写对拍找规律。

不难发现, i i i 次迭代的时候,我们会将序列里最大的元素移动到第 i i i(废话,但很关键)

那么,既然题目要求依次输出每个前缀的答案

我们不如就将这些元素一个一个插入到序列中,看看 a n s i ans_i ansi a n s i + 1 ans_{i+1} ansi+1有没有关系。

设 maxx 为前 i − 1 i-1 i1 个元素里的最大值,当我们求第 i i i 个前缀时:

(1) 若 a i < m a x x a_i < maxx ai<maxx :

“第 i i i 次迭代的时候,我们会将序列里最大的元素移动到第 i i i 位” , 那么,前i-1次迭代时, a i a_i ai 一定不会被移动(对着派蒙的代码看一下就知道了)。第 i i i 次迭代时,显然,交换次数 = 有多少个比 a i a_i ai大的数(去重)。

因此 , a n s i = a n s i − 1 ans_i = ans_{i-1} ansi=ansi1 + 比 a i a_i ai大的数的个数(去重)。

这个可以拿权值线段树维护一下

(2)若 a i = m a x x a_i = maxx ai=maxx:

因为相等不交换,因此有 a n s i = a n s i − 1 ans_i = ans_{i-1} ansi=ansi1

(3)若 a i > m a x x a_i > maxx ai>maxx:

a n s i ans_i ansi = a n s i − 1 ans_{i-1} ansi1 + 最大的数第二次出现后一共有几个数(暂时不知道怎么证明, 过两天抓个人讨论下)。

这个有点难弄,但这道题有个很好的地方在于可以打表帮助推导。

cjb :
在这里插入图片描述

所以还是那个很关键的地方-------“第 i i i 次迭代的时候,我们会将序列里最大的元素移动到第 i i i 位” ,然后打表就完事儿了.jpg。

这个可以维护两个 c n t cnt cnt, 也可以 l o w e r b o u n d ( ) lowerbound() lowerbound()一下。

代码

(记得开long long, 记得开long long, 记得开long long, 记得开long long, 记得开long long)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e6+100;
int tr[N];
void insert(int now,int l,int r,int loc,int val){
    if(l==r){
        tr[now] += val;
        tr[now] = min(1ll,tr[now]);
        tr[now] = max(tr[now],0ll);
        return;
    }
    int mid = (l+r)>>1;
    if(loc<=mid)
        insert(now<<1,l,mid,loc,val);
    else
        insert(now<<1|1,mid+1,r,loc,val);
    tr[now] = tr[now<<1]+tr[now<<1|1];
}
int query(int now,int l,int r,int ql,int qr){
    if(l>=ql&&r<=qr){
        return tr[now];
    }
    int sum = 0;
    int mid = (l+r)>>1;
    if(ql<=mid)
        sum += query(now<<1,l,mid,ql,qr);
    if(qr>=mid+1)
        sum += query(now<<1|1,mid+1,r,ql,qr);
    return sum;
}
int a[N];
vector<int> ans;
signed main(){
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        int maxx = -1;
        int last = 0;
        int tmp = 0,cnt = 0;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            if(i==1){
                ans.push_back(last);
                maxx = a[i],cnt = 1;
            }
            else{
                if(a[i]==maxx){
                    ans.push_back(last);
                    cnt++;
                    if(cnt>=2) tmp++;
                }
                else if(a[i]<maxx){
                    last = last+ query(1,1,n,a[i]+1,n);
                    ans.push_back(last);
                    if(cnt>=2) tmp++;
                }
                else{
                    last = last+2+tmp;
                    ans.push_back(last);
                    maxx = a[i];
                    tmp = 0;
                    cnt = 1;
                }
            }
            insert(1,1,n,a[i],1);
        }
        for(int i=1;i<=n;i++){
            insert(1,1,n,a[i],-1);
            cout<<ans[i-1]<<" \n"[i==n];
        }
        ans.clear();
    }
}

J. Xingqiu’s Joke(数学,数论)

题意

多次询问 ( 1 < = t < = 1 0 4 1<=t<=10^4 1<=t<=104)

给你两个正整数 a a a, b b b ( 1 < = a , b < = 1 0 9 1<=a,b<=10^9 1<=a,b<=109) , 每次操作可以选择以下三个操作中的一种:

  1. a a a b b b 同时加一

  2. a a a b b b 同时减一

  3. a a a b b b 同时除以他们的一个质公因数

    要求用最少的操作数,让 a a a, b b b中的一个变成 1 1 1

思路

发现了一个特别重要的东西 ,

a a a b b b 同时加一 , a a a b b b 同时减一 , 差值不变。

a a a b b b 同时除以他们的一个质公因数 , 差值变为原来的“该质公因数”分之一。

所以,我们假设最终的状态是 ξ = ( 1 , x ) \xi = (1,x) ξ=(1,x) , 令 d = ∣ b − a ∣ − ∣ x − 1 ∣ d = |b-a| - |x-1| d=bax1

d d d质因数分解 , 得 d = p 1 k 1 + p 2 k 2 + . . . + p n k n d = p_1^{k_1} + p_2^{k_2} + ... + p_n^{k_n} d=p1k1+p2k2+...+pnkn

那么, ( a , b ) (a,b) (a,b)必然是通过除以 k 1 k_1 k1 p 1 p_1 p1, k 2 k_2 k2 p 2 p_2 p2, k n k_n kn p n p_n pn 得到 ξ \xi ξ 的。

然后简单了,一个 1 0 9 10^9 109以内的数,它的质因数总个数不会超过 30 30 30 个 (废话*2) , 所以我们可以考虑暴力地状态转移。

f ( a , b ) f(a,b) f(a,b)表示 a , b a,b a,b达到终态所需的最小步数 , 那么 f ( a , b ) f(a,b) f(a,b)可以转移到 f ( a / k , b / k ) , f ( a + 1 , b + 1 ) , f ( a − 1 , b − 1 ) f(a/k,b/k) ,f(a+1,b+1) , f(a-1,b-1) f(a/k,b/k),f(a+1,b+1),f(a1,b1)

有点麻烦,改一下,设 f ( a , d ) f(a,d) f(a,d) 表示 a , d = ∣ a − b ∣ a, d = |a-b| a,d=ab达到终态所需的最小步数 , f ( a , d ) f(a,d) f(a,d) 可以从 f ( ⌊ a p ⌋ , d p ) f(\lfloor\frac ap\rfloor,\frac dp) f(pa,pd) f ( ⌈ a p ⌉ , d p ) f(\lceil\frac ap\rceil,\frac dp) f(pa,pd) 转移。

代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6+100;
vector<int> prime;
vector<int> p;
map<pair<int,int>,int> mp;
bool vis[N];
void pre(){
    vis[1] = 1;
    for(int i=2;i<=500000;i++){
        if(!vis[i]){
            prime.push_back(i);
            for(int j=i*2;j<=500000;j+=i)
                vis[j] = true;
        }
    }
}
void get(int d){
    for(int i=0;i<prime.size();i++){
        int now = prime[i];
        if(now>d) break;
        if(d%now==0){
            p.push_back(now);
            while(d%now==0) d/=now;
        }
    }
    if(d>1) p.push_back(d);
}
int solve(int a,int d){
    pair<int,int> pp = make_pair(a,d);
    if(a==1) return 0;
    else if(mp[pp]) return mp[pp];
    int res = a-1;
    for(auto v:p){
        if(d%v==0){
            int x = solve(a/v, d/v)+a%v; // 减完除
            int y = solve(a/v+1,d/v)+v-a%v; // 加完除
            res = min({res,1+x,1+y});
        }
    }
    mp[pp] = res;
    return res;
}
signed main(){
    ios::sync_with_stdio(false);
    pre();
    int t;
    cin>>t;
    while(t--){
        int a,b;
        cin>>a>>b;
        mp.clear(),p.clear();
        if(a>b) swap(a,b);
        if(a==1){
            cout<<0<<endl;
            continue;
        }
        else if(b-a==1){
            cout<<a-1<<endl;
            continue;
        }
        else if(a==b){
            cout<<1<<endl;
            continue;
        }
        get(b-a);
        cout<<solve(a,b-a)<<endl;
    }
    return 0;
}

I. Cloud Retainer’s Game(数学+思维)

题意

给一个二维的平面坐标系,下边界在 y = 0 y = 0 y=0 , 上边界在 y = H y = H y=H

有一个小球在 ( 0 , 0 ) (0,0) (0,0) 点处被释放, 它初始的速度向量是 v ⃗ = ( 1 , 1 ) \vec v = (1,1) v =(1,1)

平面内有 m m m 个硬币 ( x i , y i ) (x_i,y_i) (xi,yi) n n n 个障碍 ( x i ′ , y i ′ ) (x'_i,y'_i) (xi,yi)

当小球吃到硬币时,你的积分 + 1 。 当小球撞到障碍或上下边界时 , 会被反弹 (速度向量的 y y y 分量取反 , x x x 分量不变)

你可以去除任意多个障碍,问能得到的积分最大值是多少?

输入多组样例 t t t, 每个样例第一行是 H H H , 然后是 n n n个障碍,然后是 m m m个小球。

其中 2 ≤ H ≤ 1 0 9 , 1 ≤ n , m ≤ 1 0 5 , 1 ≤ x i ≤ 1 0 9 , 1 ≤ y i < H 2≤H≤10^9 , 1≤n,m≤10^5, 1≤x_i≤10^9 , 1≤y_i<H 2H109,1n,m105,1xi109,1yi<H , 多组样例保证 ( n + m ) < = 5 ∗ 1 0 5 (n+m) <= 5*10^5 (n+m)<=5105

input

2
4
3
1 1
2 2
6 2
4
3 1
3 3
5 1
7 3
3
1
4 2
3
1 1
6 2
9 1

output

3
3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w3pTx9JQ-1649140246490)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220405115407920.png)]

思路

官方解答:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-szmaNPRQ-1649140246491)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220405121345695.png)]

考虑到速度向量只能有两种情况 v ⃗ = ( 1 , 1 ) \vec v = (1,1) v =(1,1) v ⃗ = ( 1 , − 1 ) \vec v = (1,-1) v =(1,1)

我们会发现一个点只能从两条路径转移过来,因此我们希望找到一个用于描述路径信息的"偏移量",同时满足:

1.在不遇到障碍的情况下, 偏移量不变

2.一个偏移量能指定描述一条折线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T52bonH2-1649140246492)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220405140337838.png)]

然后,不难发现, 在无障碍情况下,有:

正向直线:

y = x y = x y=x

y = x − 2 ∗ H y = x-2*H y=x2H

y = x − 4 ∗ H y = x-4*H y=x4H

反向直线

y = 2 ∗ H − x y = 2*H - x y=2Hx

y = 4 ∗ H − x y = 4*H - x y=4Hx

y = 6 ∗ H − x y = 6*H - x y=6Hx

每次增加 2 ∗ H 2*H 2H

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yFPkfMTi-1649140246492)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220405141811171.png)]

那么, 当我们碰到障碍时,其实是改变了小球相对于 2*H 的偏移量:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I19s63TI-1649140246492)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220405142348553.png)]

如图, 当我们撞到一个位于 ( 4 , 4 ) (4,4) (4,4) 的障碍时 , 偏移量会比原来$ -4$

所以,我们可以找到这样一个偏移量 k:

向右下移动时 : k = ( x + y ) m o d    2 ∗ H k = (x+y) \mod 2*H k=(x+y)mod2H

向右上移动时: k = ( 2 ∗ H + x − y ) m o d    2 ∗ H k = (2*H + x - y) \mod 2*H k=2H+xy)mod2H

然后我们从最右上的点依次转移到最左下的点就好了, 显然答案就是偏移量 k = 0 k = 0 k=0时的解。

代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
map<int,int> dp;
struct Tuple{
    int x;
    int y;
    bool val;
    bool operator<(Tuple B) const{
        if(x!=B.x) return x>B.x;
        else return y>B.y;
    }
};
vector<Tuple> v;
int n,m;
int H;
signed main(){
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--){
        cin>>H;
        cin>>n;
        for(int i=1;i<=n;i++){
            int x,y;
            cin>>x>>y;
            v.push_back({x,y, false});
        }
        cin>>m;
        for(int i=1;i<=m;i++){
            int x,y;
            cin>>x>>y;
            v.push_back({x,y,true});
        }
        sort(v.begin(),v.end());
        for(auto now:v){
            int x = now.x,y = now.y;bool val = now.val;
            if(val){
                dp[(x+y)%(2*H)] += 1;
                dp[(2*H+x-y)%(2*H)] +=1;
            }
            else{
                dp[(x+y)%(2*H)] = max(dp[(x+y)%(2*H)],dp[(2*H+x-y)%(2*H)]);
                dp[(2*H+x-y)%(2*H)] = max(dp[(x+y)%(2*H)],dp[(2*H+x-y)%(2*H)]);
            }
        }
        cout<<dp[0]<<endl;
        dp.clear();
        v.clear();
    }
    return 0;
}
  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值