2023牛客寒假算法基础集训营4_20230130「向上取整」「夹逼dp」「lowbit科学+树状数组性质」「搜索」「倍增跳表」「莫队」

文章分享了作者在编程竞赛中遇到的问题和解决策略,涉及动态规划、树状数组、位运算等技术,强调了审题、理解和应用的重要性,并表达了对深化算法理解的渴望。
摘要由CSDN通过智能技术生成

6/13

教育场是有被教育到。(预计会鸽几题。

已过非太水的题们

//B
//https://ac.nowcoder.com/acm/contest/46812/B

//小构造小数学

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int a[N],b[N],c[N];

void work() {
    int n,m;cin>>n>>m;
    for(int i=0;i<n;++i){
        cin>>c[i];
    }
    if(m==2){
        for(int i=0;i<=n/2;++i){
            int now=(c[i]-c[n-1-i]+m)%m;
            if(now&1){
                cout<<"NO\n";return;
            }else{
                b[i]=now/2;
            }
            a[i]=c[i]-b[i];
            b[n-1-i]=m-b[i];
            a[n-1-i]=c[n-1-i]-b[n-1-i];
        }
    }
    else{
        for(int i=0;i<=n/2;++i){
            int now=(c[i]-c[n-1-i]+m)%m;
            if(now&1){
                b[i]=(now+m)/2;
            }else{
                b[i]=now/2;
            }
            a[i]=c[i]-b[i];
            b[n-1-i]=m-b[i];
            a[n-1-i]=c[n-1-i]-b[n-1-i];
        } 
    }
    cout<<"YES\n";
    for(int i=0;i<n;++i){
        cout<<a[i]<<" \n"[i==n-1];
    }
    for(int i=0;i<n;++i){
        cout<<b[i]<<" \n"[i==n-1];
    }
        
}

signed main() {
	io;
	work();
	return 0;
}
//C
//https://ac.nowcoder.com/acm/contest/46812/C

//夹逼dp的大暴力版

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 100 + 50;
int f[N],w[N],v[N];
int dp[N];
int dp1[N];

void work() {
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) {
        cin>>v[i]>>w[i];
    }
    
    for(int i=1;i<=n;i++){
        for(int j=m;j>=v[i];j--){
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    int ans=f[m];
    
    for(int i=1;i<=n;i++){
        memset(dp,0,sizeof dp);
        for(int j=1;j<=n;j++){
            if(j==i) continue;
            for(int k=m;k>=v[j];k--){
                dp[k]=max(dp[k],dp[k-v[j]]+w[j]);
            }
        }
        
        if(ans>dp[m]){
            cout<<"0\n";continue;
        }
        else{
            mem(dp1,0);
            for(int j=1;j<=n;j++){
                if(j==i) continue;
                for(int k=m-v[i];k>=v[j];k--){
                    dp1[k]=max(dp1[k],dp1[k-v[j]]+w[j]);
                }
            }
            cout<<ans+1-dp1[m-v[i]]-w[i]<<'\n';
        }
    }
}

signed main() {
	io;
	work();
	return 0;
}

E

谔谔,又因为语法偷懒被狂卡。向上取整试了ceil和三目运算符卡了好久好久去检查计算方法手玩各种智慧样例我觉得我对极了,然后破罐子破摔乖乖分类讨论就过了,难绷地好像吃掉了脑子里的答辩。

        赛后查了一下ceil返回的是double类型,有精度问题,但是如果强制转换可能会将1.9999999999999(2)强转成1,精度大大损失,所以一般不是很建议用。

这里有个小技巧:向上取整a/b可以写作(a+b-1)/b。

D.

D-清楚姐姐学01背包(Hard Version)_2023牛客寒假算法基础集训营4 (nowcoder.com)

思路:

好亖/不会dp/写C写了好久好久写到一半写不下去了开B甚至还做完了还是在卡C抱着写都写了的心态又调了很久终于过C不懂大家怎么写dp那么厉害。

jls处理的有点子妙的前后缀。

        对于每一个i,我们要求的是 能使 必须选i的dp最大值>不选i的dp最大值 的最小数,如果常规dp的话我们需要把ans加到v[i]里去计算还无法得知该情况是否取了i,这是我们无法求的方式。所以我们考虑将强制选i的过程与dp过程分开,在最后取i,加上v[i],这样就能直接确定差值。于是选i之前的dp就可以看作是不选i的dp,我们发现这是要求的另一个量。这里就有一个妙妙前后缀,我们开两个数组一个从前dp一个从后dp,把不选i的dp分成i之前和之后两部分。

        pre和suf分别表示选择i之前和之后(不包括i)的dp最大值。这里小坑一把,根据这个定义我们在预处理的时候就不能像往常一样dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]),因为这样处理出来的dp[i]是包括i的,我们应该处理dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]。

        下一个问题是如何选择两部分dp之和的最大值。直接贪心即可,对于每一个i,我们遍历j使前后两部分的每一种容量分配都被考虑过选出来的最大值。对于必须选i的情况,我们需要保证总容量够加w[i],即贪心遍历j只能遍历到m-w[i],而把第i个加到前部分或后部分则无所谓。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;

void work() {
    int n,m;cin>>n>>m;
    vector<int>w(n),v(n);
    for(int i=0;i<n;++i){
        cin>>w[i]>>v[i];
    }
    
    vector pre(n,vector<int>(m+1)),suf(pre);
    for(int i=0;i<n-1;++i){
        pre[i+1]=pre[i];
        for(int j=m;j>=w[i];--j){
            pre[i+1][j]=max(pre[i][j],pre[i][j-w[i]]+v[i]);
        }
    }
    for(int i=n-1;i>0;--i){
        suf[i-1]=suf[i];
        for(int j=m;j>=w[i];--j){
            suf[i-1][j]=max(suf[i][j],suf[i][j-w[i]]+v[i]);
        }
    }
    
    for(int i=0;i<n;++i){
        int val=0;//不选 Vali'
        int vali=0;//选 Valmax
        for(int j=0;j<=m;++j){
            val=max(val,pre[i][j]+suf[i][m-j]);
            if(j>=w[i]){
                vali=max(vali,pre[i][j-w[i]]+v[i]+suf[i][m-j]);
            }
            /*if(m-j>=w[i]){
                vali=max(vali,pre[i][j]+suf[i][m-j-w[i]]+v[i]);
            }*/
            //这两个判断都是对的
        }
        int ans=max(0ll,val-vali+1);
        cout<<ans<<'\n';
    }
}

signed main() {
	io;
	work();
	return 0;
}

 F.

F-清楚姐姐学树状数组_2023牛客寒假算法基础集训营4 (nowcoder.com)

思路:

 数据结构弱弱弱!lowbit水很深我不懂。之前写过一个线段树的各种序,这次是树状数组。智乃哥哥的题解小小理解了一下,我主要学的是jls的。

        我们按前序遍历查找,相当于逆推。( 刚刚跟树状数组大眼瞪小眼的时候有一瞬间脑子里飘过了期望dp的逆dfs,对我来说他们有一点点互通的感觉)。

有关lowbit在树状数组构建的二叉树中的一点性质:

  1. 对于所有左子树,二进制一定是xxx110…;对于右子树,二进制一定是xxx010…
  2. 左子树和右子树查找爸爸的过程相当于树状数组的求和 和 添加过程。(这两个过程详见2023SDNU_Winter_Practise_3 F),即+/-lowbit(x)。
  3. 对于每一个根节点x,他的左子树大小=lowbit(x) -1。
  • 证法1:令x=t+lowbit(t),即x是t的爸爸,t是x的左儿子。首先我们可以确定对于树中每一层的距离是相等的,层与层间依次是2倍,从叶节点起计,若某点深度为a,则该点与其爸爸的距离为2^(a-1)。以t为根的子树大小为2^0+2^1+……+2^(n-1)=2^n -1,其中n为t的深度。lowbit(x)=x与他爸爸之间的距离=t到x的距离*2=2^(n-1)*2=2^n=以t为根的子树大小+1。
  • 证法2:反过来思考,lowbit本身就是构建树状数组的方式,lowbit(x)是x节点负责的所有子节点,即二叉树中的左子树。(我的理解方式是,树状数组本身就是二叉树压缩而来的,压缩方式是除掉所有右子树,用lowbit的方式存储整个子树)
  1. 除去树状数组顶的点,剩下的点建出来的树是一颗完整的满的二叉树,所以对于每一对i和x-i在树中的位置完全对称,后序遍历就是前序遍历的对称过程。

其他在注释里写的很清楚了,位运算真的该好好学一学。

(jls脑子真好用啊,他一秒钟写出来的东西差点给我cpu干烧了。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;

int lowbit(int x){
    return x&(-x);
}

int get(int k,int x){
    int ans=1;
    while((1ll<<k)!=x){
        int v=__builtin_ctzll(x);//后导0的个数
        if(x>>(v+1)&1){//最后一个1前的数是1的话就是右子树
            x-=lowbit(x);
            ans+=lowbit(x);//此时x已经是根节点
            //cout<<x<<" "<<ans<<"   ";
            //我们需要加一个左子树大小+根节点
            //左子树大小=lowbit(x)-1
        }else{
            x+=lowbit(x);
            ans++;
            //cout<<x<<" "<<ans<<" 1 ";
        }
    }
    return ans;
}

void work() {
    int k,q;cin>>k>>q;
    while(q--){
        int n;cin>>n;
        if(n==(1ll<<k)){//位运算奇淫妙计
            cout<<1<<" "<<n<<" "<<n<<'\n';
        }else{
            cout<<get(k,n)<<" "<<n<<" "<<(1ll<<k)-get(k,(1ll<<k)-n)+1<<'\n';
        }
    }
}

signed main() {
	io;
	work();
	return 0;
}

J.

J-清楚姐姐学排序_2023牛客寒假算法基础集训营4 (nowcoder.com)

思路:

谔谔水题,但我不喜欢dfs。

        思考什么情况下我们可以确定一个数的排序:是当其他所有数都和他产生确定的大小关系时。(这个大小关系不一定是直接的,也可以是a>b>c,ac也已确定大小关系。 )即,比他大的数+比他小的数个数=n-1,他的位置是比他小的数的个数+1。

        我们使用标记不回溯dfs,在搜索比该数大的数时,dfs到所有比该数大的数还大的数们,层层递归寻找每一层可贡献答案的数使sum++。

Tips:

  1. 这种方法时间复杂度是O(n*m),有点危险,可以使用bitset压位降低64倍常数,魔改bfs的时候高爹讲了一下,不会。
  2. 炸鸡块哥哥讲题的时候提到一种注意,警惕有无向环的有向无环图,be like下图,对于有向图他确实没有环,但对于无向图他其实是有环的,这样的图不可以做树dp(谔谔树dp我也不会。这是一种还算常见的坑。
  • 以我的理解浅证一下错因:令dp[i]表示比i大的数的个数,a->b表示a<b。dp[4]=0,dp[2]=dp[3]=dp[4]+1=1,dp[1]=dp[2]+1+dp[3]+1=4。但其实dp[1]应该是3,多出来的1并不多在上式两个+1上,而是因为dp[2]dp[3]对答案的贡献都是‘ 4 ’却重复计算了。酱紫就是不对的!

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int sum=0;
bool vis[N];

void dfs(int i,vector<int> a[]){
    for(auto y:a[i]){
        if(!vis[y]){
            sum++;vis[y]=1;
            dfs(y,a);
        }
    }
}

void work() {
    int n,m;cin>>n>>m;
    vector<int>gr[1010],sm[1010];//比它大的,比它小的
    for(int i=1;i<=m;++i){
        int a,b;cin>>a>>b;
        gr[a].pb(b);
        sm[b].pb(a);
    }
    int dpz[1010],dpf[1010],b[1010];
    mem(dpz,0);mem(dpf,0);mem(b,-1);
    for(int i=1;i<=n;++i){
        mem(vis,0);
        dfs(i,gr);
        dpz[i]=sum;sum=0;
        mem(vis,0);
        dfs(i,sm);
        dpf[i]=sum;sum=0;
    }
    for(int i=1;i<=n;++i){
        //cout<<i<<" "<<dpz[i]<<" "<<dpf[i]<<'\n';
        if(dpz[i]+dpf[i]==n-1){
            b[dpf[i]+1]=i;
        }
    }
    for(int i=1;i<=n;++i){
        cout<<b[i]<<" \n"[i==n];
    }
}

signed main() {
	io;
	work();
	return 0;
}

 G.

G-清楚姐姐逛街(Easy Version)_2023牛客寒假算法基础集训营4 (nowcoder.com)

思路:

小小模拟。

思考何时两人会相遇:

  1. 当智乃同时或提前来到qcjj的未来轨迹中等她。
  2. qcjj在自己的轨迹里已经走到了最后一步不能再动,等智乃找到她。

可以发现如果qcjj的位置确定了,那她的行动轨迹也就确定了。

我们可以预处理出来智乃跑全图的最短路,对于每一个qcjj的位置询问,按照qc地图模拟,走每一步都比较智乃是否能先到;当qcjj走到不能走的时候,判断智乃是否能走到该位置。

可以证明智乃走到图上任何一个可走到的地方最多需要n*m步。

一开始写的bfs,写完发现爆内存了,所以就不要vis数组了,用dij最短路,用时间换空间。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
char arr[810][810];
//bool vis[810][810];
int dis[810][810];//智乃
int dx[]={1,0,0,-1};
int dy[]={0,1,-1,0};
//map<pii,bool>vis;
//#define vis[x][y] vis[m_p(x,y)]
int n,m,num=0;

bool judge(int x,int y){
    if(x>n||x<1||y>m||y<1||arr[x][y]=='#') return 0;
    else return 1;
}

void bfs(int x,int y){
    mem(dis,inf);
    //mem(vis,0);
    dis[x][y]=0;
    queue<pii> q;
    q.push(m_p(x,y));
    //vis[m_p(x,y)]=1;
    while(!q.empty()){
        pii t=q.front();q.pop();
        int xx=t.first,yy=t.second;
        for(int i=0;i<4;++i){
            int tx=xx+dx[i],ty=yy+dy[i];
            if(judge(tx,ty)){
                if(dis[xx][yy]+1<dis[tx][ty]){
                    dis[tx][ty]=dis[xx][yy]+1;
                    q.push(m_p(tx,ty));
                }
            }
        }
    }
}

int dfs(int x,int y,int cur){
    if(cur>=dis[x][y]){
        return cur;
    }
    int tx=x,ty=y;
    if(arr[x][y]=='L'){
        ty--;
    }else if(arr[x][y]=='R'){
        ty++;
    }else if(arr[x][y]=='U'){
        tx--;
    }else if(arr[x][y]=='D'){
        tx++;
    }else if(arr[x][y]=='.'){
        if(dis[x][y]<=n*m){
            return dis[x][y];
        }else return -1;
    }
    if(judge(tx,ty)){
        return dfs(tx,ty,cur+1);
    }else{
        if(dis[x][y]<=n*m){
            return dis[x][y];
        }else return -1;
    }
}

void work() {
    int sx,sy,q;cin>>n>>m>>sx>>sy>>q;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            cin>>arr[i][j];
        }
    }
    sx++;sy++;//题目给的坐标从(0,0)开始
    bfs(sx,sy);
    
    while(q--){
        int x,y;
        cin>>x>>y;
        num=0;
        x++;y++;
        int t=dfs(x,y,num);
        cout<<t<<'\n';
    }
}

int main() {
	io;
	work();
	return 0;
}

 H.

H-清楚姐姐逛街(Hard Version)_2023牛客寒假算法基础集训营4 (nowcoder.com)

思路:

倍增跳表。好厉害的想法。

        预处理出qcjj在每一个位置为起点时倍增走的路径,所以要开一个nex[N][M][logNM]大小的数组记录,nex[i][j][k]表示从(i,j)出发走2^k步到达的位置。

        跳表:先遍历图做出每一个起点第一步走到的位置。然后再次遍历图做倍增,使每一次遍历到该点时,让该点直接去到上一层去的位置去的位置。例如,第一次走了一步(a->b)(b->c),第二次再遍历到a点时,让这一次的a直接去到c(b去的位置),以此类推。可以发现每一层(k)走的步数都是上一层的2倍,即完成倍增跳表。

        首先想到,当两人相遇时,此后的每一步都可以相遇,即智乃贴着qcjj走,所以相遇过程是单调的,可以想到二分答案去找第一个相遇点,我们优化二分答案变成倍增。从2^k(最远)开始check,如果此时遇到了就说明相遇点可以更小,上次check点与本次check点对答案没有贡献,不操作;如果没遇到就说明此前不可能相遇,所以我们的ans要加本次check之前的所有步数,相遇点一定在本次check和上次check之间,我们可以更改起点为本次check点继续做倍增,可以类比为l=mid+1。

  • 这个时候不知道别人会不会有疑问,反正我一下子就有问题:在更改起点后我们倍增的范围也缩小了,如何保证在剩余遍历范围内一定可以找到相遇点?
  • 证明:这是一个非常巧妙且严格的可证问题。
  • 看下图,例如,a->e是最大范围,我们会从e点开始check,找不到就是无法相遇;如果找到了,我们就去check d点,如果d点找不到了,说明相遇点一定在de之间,d之前的所有步都无法相遇,ans首先要加上d左侧的所有步数,然后把起点变成d,这样下次check的点就是刚刚check距离的1/2,因为是倍增,所以d严格是ae中点且de距离就是刚刚check的距离,于是继续找到的下一个check点一定是de中点,如果相遇说明相遇点会在d->de中点内,没有相遇就类比a->d的操作;如果d点找到了说明相遇点一定在ad内,下一个check去查c点,以此类推。
  • 不难发现,对于每一次check的结果,剩下的未确定范围恰好是2^i,而剩余可查找的步数是2^0+2^1+……+2^(i-1)=2^i - 1,这个-1恰好是两点之间的最小距离,最后一段左边最右点一定是差一步就相遇的点,最后一段右边最左点一定是恰好相遇的点。即进行完倍增查找之后,我们恰好把所有点都考虑到了。

由于上述倍增查找过程中,所有对答案的贡献都是恰好没相遇的距离,所以要求恰好相遇时ans+=1。

        什么时候是无法相遇(-1)的情况:ans==2^k的时候,这是因为在无法相遇的时候,每次的check都是无法相遇的,答案每次都会增加2^i,出循环时ans=2^0+……+2^i=2^(i+1) -1,(ans+=1)=2^(i+1)。

(以下代码由于便利使数组下标从1开始作为有效数字,所以文字中的i==代码里的i-1)

        ps:其实倍增套二分也能过,但是智乃哥哥说如果你这样想就说明你根本不会倍增也不会二分,倍增和二分本质是一样的。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
char arr[810][810];
//bool vis[810][810];
int dis[810][810];//智乃
const int dx[]={-1,0,1,0,0};
const int dy[]={0,1,0,-1,0};
pii nex[810][810][25];
unordered_map<char,int>dir={{'U',0},{'R',1},{'D',2},{'L',3},{'.',4}};
int n,m,num=0;
 
bool judge(int x,int y){
    if(x>n||x<1||y>m||y<1||arr[x][y]=='#') return 0;
    else return 1;
}

void bfs(int x,int y){
    mem(dis,inf);
    //mem(vis,0);
    dis[x][y]=0;
    queue<pii> q;
    q.push(m_p(x,y));
    //vis[m_p(x,y)]=1;
    while(!q.empty()){
        pii t=q.front();q.pop();
        int xx=t.first,yy=t.second;
        for(int i=0;i<4;++i){
            int tx=xx+dx[i],ty=yy+dy[i];
            if(judge(tx,ty)){
                if(dis[xx][yy]+1<dis[tx][ty]){
                    dis[tx][ty]=dis[xx][yy]+1;
                    q.push(m_p(tx,ty));
                }
            }
        }
    }
}

void work() {
    int sx,sy,q;cin>>n>>m>>sx>>sy>>q;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            cin>>arr[i][j];
        }
    }
    sx++;sy++;//题目给的坐标从(0,0)开始
    bfs(sx,sy);
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            if(arr[i][j]!='#'){
                int x=i+dx[dir[arr[i][j]]];
                int y=j+dy[dir[arr[i][j]]];
                if(arr[x][y]=='#') nex[i][j][1]=m_p(i,j);
                else nex[i][j][1]=m_p(x,y);
            }
        }
    }
    for(int k=2;k<=22;++k){
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                if(arr[i][j]!='#'){
                    int x=nex[i][j][k-1].first;
                    int y=nex[i][j][k-1].second;//上层所在位置
                    nex[i][j][k]=nex[x][y][k-1];//去了上层去的位置去的位置
                }
            }
        }
    }
    while(q--){
        int x,y;cin>>x>>y;x++;y++;
        int ans=0;//qcjj走的步数
        for(int i=20;i>=1;--i){
            auto tx=nex[x][y][i].first;
            auto ty=nex[x][y][i].second;
            if(dis[tx][ty]>n*m||dis[tx][ty]>ans+(1<<(i-1))){//到不了||没追上
                ans+=1<<(i-1);
                x=tx;y=ty;
            }
        }
        if(++ans==1<<20)cout<<"-1\n";
        else cout<<ans<<'\n';
    }
    
}

signed main() {
	io;
	work();
	return 0;
}

I.

I-清楚姐姐采蘑菇_2023牛客寒假算法基础集训营4 (nowcoder.com)

2023.2.20更新----->莫队我来辣!

前置莫队题目:

P2709 小B的询问 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

P1494 [国家集训队] 小 Z 的袜子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

说一下这个题:

Problem - C - Codeforces

用了一个很常见的莫队优化,就是在排序的时候如果所属的块是奇数就递增,偶数递减排序,这样我们就能做到一个贪吃蛇(?样子的莫队,达到优化。

struct 	Query {
	int l, r, id, pos;
	bool operator <(const Query &x) {
		if (pos == x.pos) {
			if (pos & 1) {
				return r < x.r;
			} else {
				return r > x.r;
			}
		} else
			return pos < x.pos;
	}
} a[N];

感觉分块是一种有魔法的暴力,,,还学了一下O(n)求中位数。

思路:

看懂了感觉其实不是特别难。注意该题的xy坐标轴和正常的不太一样。

首先由于莫队的指针是单调的,所以ban掉一个方向是无所谓的。如果不能走某个方向,我们就按他的反方向分块建图,(大概下图酱紫)然后分类讨论。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
string ans;
int n,m,block;
char X;
int x=0,y=0;

void D(){x=(x+1)%n;ans+='D';}
void U(){x=(x?x-1:n-1);ans+='U';}
void R(){y=(y+1)%n;ans+='R';}
void L(){y=(y?y-1:n-1);ans+='L';}

bool cmp(const pii &a,const pii &b){
    if(X=='R'||X=='L'){
        if(a.fi/block!=b.fi/block) return a.fi/block<b.fi/block;
        else return (X=='R'?a.se>b.se:a.se<b.se);
    }else{
        if(a.se/block!=b.se/block) return a.se/block<b.se/block;
        else return (X=='U'?a.fi<b.fi:a.fi>b.fi);
    }
}

void work() {
    cin>>n>>m>>X;
    block=sqrt(n+0.5);
    vector<pii>node(m);
    for(int i=0;i<m;++i){
        cin>>node[i].fi>>node[i].se;
    }
    sort(node.begin(),node.end(),cmp);
    for(int i=0;i<m;++i){
        while(x>node[i].fi&&X=='U')D();
        while(x<node[i].fi&&X=='D')U();
        while(y>node[i].se&&X=='L')R();
        while(y<node[i].se&&X=='R')L();
        while(x>node[i].fi)U();
        while(x<node[i].fi)D();
        while(y>node[i].se)L();
        while(y<node[i].se)R();
    }
    cout<<ans<<'\n';
}

signed main() {
	io;
	work();
	return 0;
}

小小总结

 很不会dp,感觉C过了一车人我还在调。回家前想着假期好好学dp,谔谔,现在是又没学新的又忘了旧的。

审题审题审题!!!写G写到一半了发现读了假题没时间了,前面E的向上取整卡太久了,坏。

jls的武器就是超级智慧的大脑,xmsl脑子好用还努力。

这几次写题解有时候会觉得某一个点和之前的什么东西有点像,往回翻发现还没有一个月前的东西我就快忘没了(我这不争气的记性),更恐怖的是以前我做出来了的题现在有点不会了。今天干脆大改一通标题闲的没事就看看别忘得太快。

在思考下一个阶段的重心是(补题or vp) && (回顾已知加深理解 or 多见新题),假期比赛基本上都补了个差不多,硬补了很多赛时根本没思路的题,但是并没有达到预期效果,补题更像是学习vp可能会更偏重时限效率和赛时持续思考的锻炼。很多典并没有什么新算法,就是更深的理解或者干脆就是想不想得到这么做,但是我又没那么聪明没见过典就能永远自己想出来,但是比赛也不可能总出裸典题,一些变化其实还是要看对已知算法的理解。下学期需要找找平衡。

可能快开学了有一些开学焦虑做什么事情都静不下心来,效率低低低。想想我的假期可以说是荒废时间,还留下了好多坑,感觉学到的最现实的东西就是这个假期过完了我怎么没有长进保持这个状态可能明年今日也还是这个样子。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值