2026杭电多校春季训练赛日志

赛时出了四题,感觉和队里大佬差距还是很大啊,但是反过来想,还是有很大提升空间的()

1009临渊羡鱼

就是最大值和最小值差值+1,直接写就好

#include <bits/stdc++.h>
using namespace std;
/*



*/
#define ll long long
#define double long double 
#define pii pair<ll,ll> 
void sol() {
    int n;
    cin>>n;
    vector<int> a(n);
    for(int i=0;i<n;i++) cin>>a[i];
    cout<<*max_element(a.begin(),a.end())-*min_element(a.begin(),a.end())+1<<'\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;
    while (t--) {
        sol();
    }
    return 0;
}

1007最大三段和

因为中间的只有一个点,我们可以在\(k\in[3,n-2]\)范围上枚举中间点,答案=\(max\{pre_{k-2}+a_k+suf_{k+2}\}\),其中\(pre_i,suf_i\)分别表示\(i\)位置之前的最大子段和以及\(i\)位置之后的最大子段和。我们可以先算出强制选每一个点的前缀最大子段和和后缀最大子段和,即\(pre_i=max\{pre_{i-1},0\}+a_i\)\(suf_i=max\{suf_{i+1},0\}+a_i\),然后再求出\(i\)位置之前的最大子段和以及\(i\)位置之后的最大子段和,即\(pre_i=max\{pre_i,pre_{i-1}\}\)\(suf_i=max\{suf_i,suf_{i+1}\}\)

#include <bits/stdc++.h>
using namespace std;
/*



*/
#define ll long long
#define double long double 
#define pii pair<ll,ll>

// int gcd(int a,int b){
//     return b?gcd(b%a,a):a;
// }

void sol() {
    int n;
    cin>>n;
    vector<ll> a(n+1,0),pre(n+1,0),suf(n+1,0);
    for(int i=1;i<=n;i++) cin>>a[i];
    pre[1]=a[1],suf[n]=a[n];
    for(int i=2;i<=n;i++) pre[i]=max(pre[i-1],0ll)+a[i];
    for(int i=n-1;i>=1;i--) suf[i]=max(suf[i+1],0ll)+a[i];
    for(int i=2;i<=n;i++) pre[i]=max(pre[i],pre[i-1]);
    for(int i=n-1;i>=1;i--) suf[i]=max(suf[i],suf[i+1]);
    ll ans=INT64_MIN;
    for(int i=3;i<=n-2;i++){
        ans=max(ans,a[i]+pre[i-2]+suf[i+2]);
    }
    cout<<ans<<'\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;
    while (t--) {
        sol();
    }
    return 0;
}

1001最大的公约数

自己在这道题上卡了很久,最开始尝试从\(gcd(a,n-a)\)出发但是做不下去,写了1007之后有了点灵感。
对于这种和数论有关的题,可以从质因数分解或者分解成最大公因数和一个因子的角度出发。从比较自然的角度解释就是,我们希望找出\(gcd\)的最大值,可以找找\(gcd\)满足哪些公式和数量关系,然后进行转化。
那么容易知道\(a=k_1*g,b=k_2*g\),其中\(k_1>k_2\ge 1\),这里我们把\(gcd\)简写成\(g\)。然后我们还知道\(a+b=(k_1+k_2)*g=n \iff g= \frac{n}{k_1+k_2}\),由此可见,我们只需要找出不为\(n\)的最大的\(n\)的因数就可以,注意这里\(k_1+k_2>2\),所以说\(g=\frac{n}{2}\)这个因子是不能放进去的。按照上述条件我们收集所有符合条件的因数并从大到小排序就可以。

代码

#include <bits/stdc++.h>
using namespace std;
/*



*/
#define ll long long
#define double long double 
#define pii pair<ll,ll>


void sol() {
    int n;
    cin>>n;

    vector<int> ans;
    ans.push_back(1);
    for(int i=2;i*i<=n;i++){
        if(n%i==0){
            if(i==2) ans.push_back(2);
            else{
                ans.push_back(i);
                ans.push_back(n/i);
            }
        }
    }
    sort(ans.begin(),ans.end(),greater<int>());
    cout<<ans[0]<<'\n';
    
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;
    while (t--) {
        sol();
    }
    return 0;
}

1003 杂草蔓延

这道题想了好久,首先我们令\(a=min(n,m),b=max(n,m)\),然后变成一个横向长度为\(a\),纵向长度为\(b\)的长方形,方便讨论。当\(a=1\)的时候,显然隔一个放一个是最优解。当\(a \ge 2\)的时候,因为一个\(2*2\)的格子我们可以通过放对角线进行构造。那么如果是\(3*3\)及更大的正方形,我们也可以采用在对角线上放草这种做法进行构造,那么正方形放完了,还剩下\(b-a\)长度,显然我们隔一个放一个就可以。
不是很好想,但是代码就寥寥几行...

代码

#include <bits/stdc++.h>
using namespace std;
/*



*/
#define ll long long
#define double long double 
#define pii pair<ll,ll>


void sol() {
    int n,m;
    cin>>n>>m;
    int a=min(n,m),b=max(n,m);
    if(a==1){
        cout<<b/2+1<<'\n';
    }else{
        cout<<a+(b-a+1)/2<<'\n';
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;
    while (t--) {
        sol();
    }
    return 0;
}

1002 雪蜜冰城柠檬茶的神秘成分魔法

自己这道题写了俩小时没写出来,写的太史了。自己赛时的想法是对每个权值维护一个并查集,然后记录下每个权值的并查集的头的编号,以及并查集的头的编号的权值。然后直接进行模拟,比赛的时候因为少写了一行代码就WA了...

(这段时间学Web开发追求变量命名规范意义明确然后在这段代码里面采用了驼峰式命名,但还是改变不了屎山代码的本质...)

代码(史)

#include <bits/stdc++.h>
using namespace std;
/*

    对每一个点维护一个并查集
    然后map存并查集的头部

    搞两个map存一下映射吧...

*/
#define ll long long
#define double long double 
#define pii pair<ll,ll>

struct DSU{
    int n;
    //第一个是值到并查集头,第二个是并查集头到值
    map<int,int> ValToHead,HeadToVal;
    vector<int> siz,fa,a;
    // DSU(int n_):n(n_),siz(n+1,1),fa(n+1),a(n+1){
    //     for(int i=1;i<=n;i++) fa[i]=i;
    // }

    DSU(int n_):n(n_),siz(1,1),fa(1,1),a(1,1){

    }

    void add(int i){
        siz.push_back(1),fa.push_back(i);
        auto it=ValToHead.find(a[i]);
        if(it==ValToHead.end()){
            ValToHead[a[i]]=fa[i];
            HeadToVal[fa[i]]=a[i];
        }else{
            siz[it->second]++;
            fa[i]=it->second;
        }
    }
    
    int find(int x){
        return x==fa[x]?x:fa[x]=find(fa[x]);
    }
    void change(int x,int y){
        // if(x==y) return ;
        auto check=ValToHead.find(x);
        int fx;
        if(check==ValToHead.end()) return ;
        else fx=check->second;
        auto it=ValToHead.find(y);
        if(it==ValToHead.end()){
            ValToHead[y]=fx;
            HeadToVal[fx]=y;
            ValToHead.erase(x);
        }else{
            int fy=it->second;
            siz[fy]+=siz[fx];
            fa[fx]=fy;
            ValToHead.erase(x);
            HeadToVal.erase(fx);
        }
    }

    void clear(int x){
        auto it=ValToHead.find(x);
        if(it==ValToHead.end()) return ;
        else{
            int fx=it->second;
            HeadToVal.erase(fx);    
            ValToHead.erase(x);
        }
    }

};


void sol() {
    int n,m;
    cin>>n;
    DSU dsu(n);
    for(int i=1;i<=n;i++){
        int x;cin>>x;
        dsu.a.push_back(x);
        dsu.add(i);
    }

    // for(int i=1;i<=n;i++){
    //     int fx=dsu.find(i);
    //     cout<<dsu.HeadToVal[fx]<<" ";
    // }


    cin>>m;
    int sum=n;
    while(m--){
        int op;
        cin>>op;
        if(op==1){
            int x,y;
            cin>>x>>y;
            dsu.change(x,y);
        }else if(op==2){
            int x;cin>>x;
            sum++;
            dsu.a.push_back(x);
            dsu.add(sum);
        }else{
            int x;cin>>x;
            dsu.clear(x);
        }
        // for(int i=1;i<=sum;i++){
        //     int fi=dsu.find(i);
        //     auto it=dsu.HeadToVal.find(fi);
        //     if(it!=dsu.HeadToVal.end()) cout<<it->second<<" ";
        // }
        // cout<<'\n';
    }
    for(int i=1;i<=sum;i++){
        int fi=dsu.find(i);
        auto it=dsu.HeadToVal.find(fi);
        if(it!=dsu.HeadToVal.end()) cout<<it->second<<" ";
    }
    cout<<'\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;
    while (t--) {
        sol();
    }
    return 0;
}

1010 收买时间

求最小值,不难想到二分,比如当前求出来最小时间,那么大于等于最小时间一定可以,小于最小时间一定不行。然后我们思考check函数怎么写,我们需要判断起点终点是否联通。

比如当前check的是时间\(t\),想一想哪些格子可以走,相邻的时间均\(\le t\)的点会形成一个联通块,它们内部是联通的,相当于都在时间\(t\)及其之前发生。然后原图就会变成一个一个联通块,我们现在需要判断是否可以在代价不超过金币的情况下达到起点所在联通块和终点所在联通块联通。因为还有传送门,相当于我们从一个联通块向另一个联通块建立一条代价为\(w\)的边(这里需要判断这条边两侧的点是否都在联通块当中)。
那么现在,我们建立了一张新的图,我们的目的是判断在路径边权和不超过\(money\)的情况下,是否能从起点到达终点,我们只需要跑一遍dijkstra算法就可以。
总的时间复杂度\(O(\log(1e9)*(nm+k\log(k)))\)

#include <bits/stdc++.h>
using namespace std;
/*



*/
#define ll long long 
#define pii pair<ll,ll>

const int N=410;
ll mp[N][N],num[N][N];
int cnt;
int n,m,money,k;
int dir[5]={-1,0,1,0,-1};

struct Point{
    ll x,y;
};


struct node{
    ll dis;
    int u;
    bool operator<(const node&other)const{
        return dis>other.dis;
    }
};

struct Edge{
    Point u,v;
    ll w;
};

vector<Edge> edge;

bool check(ll t){
    cnt=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            num[i][j]=0;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(mp[i][j]>t||num[i][j]) continue;
            queue<Point> q;
            q.push({i,j});
            num[i][j]=++cnt;
            while(q.size()){
                auto [x,y]=q.front();q.pop();
                for(int i=0;i<=3;i++){
                    int fx=x+dir[i],fy=y+dir[i+1];
                    if(fx>=1&&fx<=n&&fy>=1&&fy<=m&&mp[fx][fy]<=t&&num[fx][fy]==0){   
                        num[fx][fy]=cnt;
                        q.push({fx,fy});  
                    }
                }
            }
        }
    }
    
    vector<vector<pii>> e(cnt+1);
    for(int i=0;i<k;i++){
        auto [u,v,w]=edge[i];
        if((num[u.x][u.y]&&num[v.x][v.y])&&(num[u.x][u.y]^num[v.x][v.y])){
            e[num[u.x][u.y]].push_back({num[v.x][v.y],w});
        }
    }

    if(num[1][1]==num[n][m]) return true;

    vector<ll> dis(cnt+1,1e16),vis(cnt+1,0);
    priority_queue<node,vector<node>> q;
    dis[num[1][1]]=0;
    q.push({0,num[1][1]});
    while(q.size()){
        auto [d,u]=q.top();q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(auto [v,w]:e[u]){
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push({dis[v],v});
            }
        }
    }
    if(dis[num[n][m]]<=money) return true;
    else return false;
}

void sol() {

    cin>>n>>m>>money>>k;
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>mp[i][j];
        }
    }
    edge.resize(k);
    for(int i=0;i<k;i++){
        cin>>edge[i].u.x>>edge[i].u.y>>edge[i].v.x>>edge[i].v.y>>edge[i].w;
    }
    ll l=0,r=1e12;
    ll ans=r;
    while(l<=r){
        ll mid=(l+r)>>1;
        if(check(mid)){
            ans=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    cout<<ans<<'\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;
    while (t--) {
        sol();
    }
    return 0;
}


1004 小z的开箱

这道题的数据范围很特殊,每次加的数最多不超过\(10\),这意味着一旦走到了某个质数之和,和它后面一个质数的差一旦超过了10,就不再可能是答案了,这个数是第30个质数(113),那么也就是说答案不会超过113。

接下来考虑怎么取数,因为这是个环形数组,我们把它重复两遍转化为一条链。首先我们需要枚举每个点作为起点,然后我们看当前数\(cur\)能不能走到下一个质数\(x\),也就是说我们需要找在它的位置之后的数组当中的元素\(a_i\)使得\(a_i\ge x-cur\),如果有的话一定是要取的,只要有这样的数满足条件就要取,因为如果不取这个数的话,当再次去一个这个位置之后的宝石的时候,不取的值一定小于等于取的值,那么取了这个宝石之后也仍然是小于等于的关系,所以说取一定是最优。

所以说我们要维护一个\(next_{i,j}\),表示\(i\)位置为开头的包含自己的宝石情况下,第一个数值\(\ge j\)的宝石的位置。直接逆序维护就好。因为顺时针和逆时针都有可能成为答案,所以我们要分别算一遍。

代码

#include <bits/stdc++.h>
using namespace std;
/*



*/
#define ll long long 

const int N=2e5+2;
int nxt[N][11];

int prime[32]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113};

void sol() {
    int n;
    cin>>n;
    vector<int> a(2*n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
        a[i+n]=a[i];
    }
    for(int i=1;i<=2*n;i++){
        for(int j=0;j<=10;j++){
            nxt[i][j]=-1;
        }
    }
    vector<int> p(11,-1);
    for(int i=2*n;i>=1;i--){
        for(int j=1;j<=a[i];j++) p[j]=i;
        for(int j=1;j<=10;j++) nxt[i][j]=p[j];
    }

    int ans=0;
    for(int st=1;st<=n;st++){
        int cur=st,i=0,sum=0;
        // cout<<i<<"\n";
        while(i<30&&cur<st+n){
            // cout<<cur<<' ';
            int del=prime[i+1]-prime[i];
            if(nxt[cur][del]==-1||nxt[cur][del]-st>n-1) break;
            else{
                while(i<30&&prime[i+1]<=sum+a[nxt[cur][del]]){
                    ans=max(ans,prime[i+1]);
                    i++;
                }
                cur=nxt[cur][del]+1;
                sum=prime[i];
            }     
        }
        // cout<<'\n';
    }
    // for(int i=1;i<=n;i++){
    //     for(int j=0;j<=10;j++){
    //         cout<<i<<' '<<j<<' '<<nxt[i][j]<<'\n';
    //     }
    // }
    reverse(a.begin()+1,a.begin()+n+1);
    reverse(a.begin()+n+1,a.end());
    for(int i=0;i<=10;i++) p[i]=-1;
    for(int i=1;i<=2*n;i++){
        for(int j=0;j<=10;j++) nxt[i][j]=-1;
    }
    for(int i=2*n;i>=1;i--){
        for(int j=1;j<=a[i];j++) p[j]=i;
        for(int j=1;j<=10;j++) nxt[i][j]=p[j];
    }
    for(int st=1;st<=n;st++){
        int cur=st,i=0,sum=0;
        while(i<30&&cur<st+n){
            int del=prime[i+1]-prime[i];
            if(nxt[cur][del]==-1||nxt[cur][del]-st>n-1) break;
            else{
                while(i<30&&prime[i+1]<=sum+a[nxt[cur][del]]){
                    ans=max(ans,prime[i+1]);
                    i++;
                }
                cur=nxt[cur][del]+1;
                sum=prime[i];
            }     
        }
    }
    cout<<ans<<'\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;
    while (t--) {
        sol();
    }
    return 0;
}

1005 时空碎片收集者

一道dp好题,首先这个肯定和操作的种类有关系,但是单纯按照种类划分,会有后效性,即如果你选了第二种操作,你不知道它是第几个受到影响的点,所以说我们应该开一维表示是第几个点被影响的,也就是它后面还有多少个点没有被影响。

由于k的范围很小,我们可以定义\(dp[i][j]\)表示来到\(i\)时空节点 并且 后续还有\(j\)个节点被时空回溯影响(包括\(i\)点)

  • \(j=0\)代表当前没有被回溯影响
  • \(1 \le j \le k:\)节点\(i\)被回溯影响,且节点\(i\)后续\(j-1\)个节点被回溯影响
  • \(j=k+1\)在节点\(i\)使用时空回溯,后续\(k\)个节点被影响

对于每个节点\(i\),考虑以下转移:

  1. 从节点\(i-1\)直接走来(不跳跃)
    节点\(i\)不被影响(\(j=0\)),从节点\(i-1\)\(j=0\)\(j=1\)状态转移而来,加当前节点原始碎片值:
    \(dp[i][0]=max(dp[i][0],max(dp[i-1][0],d[i-1][1])+a[i])\)
  2. 节点\(i\)被影响(\(1 \le j \le k\)),从节点\(i-1\)\(j+1\)状态延续而来:
    \(dp[i][j]=max(dp[i][j],dp[i-1][j+1]+\lfloor \frac{a[i]}{2}\rfloor)\)
  3. 在节点\(i\)使用时空回溯(只能从不受影响的状态触发)
    \(dp[i][k+1]=max(dp[i][k+1],max(dp[i-1][0],dp[i-1][1])+2*a[i])\)
  4. 从节点\(i-1\)使用时空跳跃
    从节点\(i-1\)的状态\(j\),跳跃到节点\(j+m+1\),收集从\(i\)\(i+m\)的原始碎片(不受回溯影响):
    \(dp[min(i+m,n)][max(0,j-m-1)=max(...,dp[i-1][j]+pre[min(i+m,n)]-pre[i-1])\)
    其中:\(pre[i]\)表示原始碎片前缀和。

虽然跳到了\(i+m+1\)节点,但\(i+m+1\)的选择还未确定,所以这里转移到\(i+m\)节点,影响回溯的节点比\(i-1\)少了\(m+1\)个,即\(max(0,j-m-1)\)

最终答案:\(max_{0 \le j \le k+1}dp[n][j]\)

代码

#include <bits/stdc++.h>
using namespace std;
/*



*/
#define ll long long 

const  int N=1e5+10;
ll f[N][26];

void sol() {
    int n,m,k;
    cin>>n>>k>>m;
    vector<ll> a(n+1,0),pre(n+1,0);
    for(int i=1;i<=n;i++){
        cin>>a[i];
        pre[i]=pre[i-1]+a[i];
    
        for(int j=0;j<=k+1;j++){
            f[i][j]=0;
        }
    }

    for(int i=1;i<=n;i++){
        f[i][0]=max(f[i][0],max(f[i-1][0],f[i-1][1])+a[i]);
        f[i][k+1]=max(f[i][k+1],max(f[i-1][0],f[i-1][1])+2*a[i]);
        for(int j=1;j<=k;j++)  f[i][j]=max(f[i][j],f[i-1][j+1]+a[i]/2);
        for(int j=0;j<=k+1;j++) f[min(n,i+m)][max(0,j-m-1)]=max(f[min(n,i+m)][max(0,j-m-1)],f[i-1][j]+pre[min(i+m,n)]-pre[i-1]);
    }

    cout<<*max_element(f[n],f[n]+k+2)<<'\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;
    while (t--) {
        sol();
    }
    return 0;
}

1008 单十一花钱计划

这道题学习的题解,首先\(min(a+x,b+y)\)这个式子我们尝试对它进行转化,如果\(a+x<b+y \iff a-b<y-x\)\(a-b<y-x\)的时候最小值为\(a+x\),反之\(a-b\ge y-x\)的时候最小值为\(y+b\),但是这个时候还是不明显。现在我们把每个衣服的取值转化成了自己的属性\(a,b\)和外在条件\(y-x\)的关系,我们可以极端的想一下,如果\(y-x\)非常小会怎么样?即\((y-x)\rightarrow -\infty\)的时候,那么每一个\(a-b\ge y-x\)都是成立的,即每一个衣服的取值都是\(b+y\),然后随着\(y-x\)一点点的变大,会有一部分\(a-b\ge y-x\)的等式失效,那么这些失效部分的取值就从\(y+b\)变为了\(a+x\)

这是一个强制在线问题。如果说这个问题是离线的,有一种方法就是我们可以对\(y-x\)进行排序,然后从小到大沿着\(b-a\)这个轴去扫一遍,每次在\(y-x\)左侧(含端点)的所有的衣服都是\(b+y\)的取值,在其右侧的都是\(a+x\)的取值,然后分别开关于\(a\)的线段树和\(b\)的线段树,维护区间最大值。现在是在线的,那么对于每个\(y-x\),它左侧的点都是取值为\(b+y\)的,右侧的点都是取值为\(a+x\)的,那么就可以对b和a分别开一个可持久化线段树维护从左到右每个元素\(b+y\)时候的区间最大值。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
/*



*/
#define ll long long 
const int M=18000000,N=5e5+10;
ll tr1[M],tr2[M],ans1=-1e18,ans2=-1e18;
int ls1[M],ls2[M],rs1[M],rs2[M],root1[N],root2[N],idx1,idx2,n;
struct node{
    ll a,b;
    int i;
    bool operator<(const node&other)const{
        return b-a<other.b-other.a;
    }
}clo[N];

void up1(int i){
    tr1[i]=max(tr1[ls1[i]],tr1[rs1[i]]);
}

void up2(int i){
    tr2[i]=max(tr2[ls2[i]],tr2[rs2[i]]);
}

int build(int l,int r,int &idx2){
    int i=++idx2;
    if(l==r){
        tr2[i]=clo[l].a;
    }else{
        int mid=(l+r)>>1;
        ls2[i]=build(l,mid,idx2);
        rs2[i]=build(mid+1,r,idx2);
        up2(i);
    }
    return i;
}

int del(int ji,int l,int r,int pre,int &idx2){
    int i=++idx2;
    if(l==r){
        tr2[i]=-1e18;
    }else{
        ls2[i]=ls2[pre],rs2[i]=rs2[pre];
        int mid=(l+r)>>1;
        if(ji<=mid){
            ls2[i]=del(ji,l,mid,ls2[pre],idx2);
        }else{
            rs2[i]=del(ji,mid+1,r,rs2[pre],idx2);
        }
        up2(i);
    }
    return i;
}

int add(int ji,int l,int r,int pre,int &idx1,int b){
    int i=++idx1;
    if(l==r){
        tr1[i]=b;
    }else{
        ls1[i]=ls1[pre],rs1[i]=rs1[pre];
        int mid=(l+r)>>1;
        if(ji<=mid){
            ls1[i]=add(ji,l,mid,ls1[pre],idx1,b);
        }else{
            rs1[i]=add(ji,mid+1,r,rs1[pre],idx1,b);
        }
        up1(i);
    }
    return i;
}

void query1(int jl,int jr,int l,int r,int i){
    if(!i) return ;
    if(jl<=l&&r<=jr) ans1=max(ans1,tr1[i]);
    else{
        int mid=(l+r)>>1;
        if(jl<=mid) query1(jl,jr,l,mid,ls1[i]);
        if(jr>=mid+1) query1(jl,jr,mid+1,r,rs1[i]);
    }
}

void query2(int  jl,int jr,int l,int r,int i){
    if(!i) return ;
    if(jl<=l&&r<=jr) ans2=max(ans2,tr2[i]);
    else{
        int mid=(l+r)>>1;
        if(jl<=mid) query2(jl,jr,l,mid,ls2[i]);
        if(jr>=mid+1) query2(jl,jr,mid+1,r,rs2[i]);
    }
}

void sol() {
    int n,q;cin>>n>>q;
    tr1[0]=tr2[0]=-1e18;
    for(int i=1;i<=n;i++){
        cin>>clo[i].a>>clo[i].b;clo[i].i=i;
    }
    root2[0]=build(1,n,idx2);
    sort(clo+1,clo+1+n);
    for(int i=1;i<=n;i++){
        root1[i]=add(clo[i].i,1,n,root1[i-1],idx1,clo[i].b);
        root2[i]=del(clo[i].i,1,n,root2[i-1],idx2);
    }
    ll last=0;
    while(q--){
        int l,r; ll x,y;cin>>l>>r>>x>>y;
        x=last^x,y=last^y;
        ans1=ans2=-1e18;
        int left=1,right=n,mid,cur=0;
        while(left<=right){
            mid=(left+right)>>1;
            if(clo[mid].b-clo[mid].a<=x-y){
                cur=mid;
                left=mid+1;
            }else right=mid-1;
        }
        query1(l,r,1,n,root1[cur]),query2(l,r,1,n,root2[cur]);
        if(ans1!=-1e18) ans1=ans1+y;
        if(ans2!=-1e18) ans2=ans2+x;
        last=max(ans1,ans2);
        cout<<last<<'\n';
        ans1=ans2=-1e18;
    }
    idx1=idx2=0;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1;
    cin >> t;

    while (t--) {
        sol();
    }
    return 0;
}

已经好久没有这么高强度的训练了,确实有一种赛场的紧张感。
这场比赛的知识上的经验就是

  • 对数论这种问题如果想不出来可以尝试质因数分解,分解最大公因数等方法,然后尝试去找答案和已知的数量关系进行建模。
  • 然后求最小值可以考虑二分,当然需要注意是否满足单调性
  • 判断图是否联通,可以采用最短路。
  • 注意条件转化,如果满足当前条件可以转化为什么条件
  • 注意数据范围
  • 正难则反
  • dp设计的时候要能完整表示状态,无后效性,如果有某一个时的操作有持续影响的,应该把剩余持续时间当做dp的一个维度。这种情况下注意考虑还剩多少时间和已经花多少时间哪个要更容易一些
  • 注意min max函数的转化
  • 强制在线可考虑可持久化线段树
  • 极端思想

以及,菜就多练...

posted @ 2026-03-13 23:52  1445141  阅读(96)  评论(0)    收藏  举报