2023牛客暑期多校训练营9

D.Non-Puzzle:Error Permulation

利用双指针和差分

假设现在的区间是以x作为左端点,然后如果[x,y]这段区间是不符合条件的,那么就标记一下y这个位置,当我们枚举以x为左端点的区间时,[x,y]这段区间就不符合,我们就需要搞一个数组,记录当以i为左端点时,以y为右端点不满足条件,可以在y这个位置加一个1来标记一下,但是以x为左端点时,可能连续的一段区间作为右端点都是不满足条件的,那么我们就需要将整个区间的每个数都加一个1来标记一下,此时可以利用差分数组来操作,然后最后求一遍前缀和就可以得到以x为左端点,y是否标记为1了(有可能以x为左端点,然后y被标记了好几次,即加了好多次1,但是没关系,只要y被标记过,就不是0了,不是0就说明[x,y]这段区间不满足条件)

最后我们只要从左到右枚举每一个数,枚举以它为左端点的所有区间(这样可以做到不重不漏地枚举所有区间),实际上我们是在枚举右端点,只要右端点没有被标记过,那么该区间符合条件,就计数+1

具体标记的方法:

对于每一个数,首先我们枚举包含它的所有不满足条件的区间,即将该数作为区间的第一个位置它又是第一小的,将该数作为区间的第二个位置它又是第二小的...(具体看注释)

然后如果区间[x,y]不满足条件,那么以x为左端点,就标记一下y

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
#include<vector>
#include<cstdio>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N=5010;
int a[N];
int d[N][N];
int n;
void solve() {
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=0;i<=n+1;i++){
        for(int j=0;j<=n+1;j++){
            d[i][j]=0;
        }
    }
    for(int i=1;i<=n;i++){
        int l=i,r=i;
        while(r<n&&a[r+1]>a[i]) r++;//首先是a[i]作为区间的第一个数同时又是第一小的
        d[i][l]++,d[i][r+1]--;//以i为左端点,[l,r]这整段区间都标记为1,表示以i为左端点,[i,l],[i,l+1],[i,l+2],...[i,r]都不符合条件
        //接下来开始找a[i]作为区间的第二个数同时又是第二小的,a[i]作为区间的第三个数同时又是第三小的...
        for(int j=i-1;j>=1;j--){
            //如果a[j]本来就小于a[i],那么以j为左端点,[l,r]这整段区间也都标记为1,表示以j为左端点,[j,l],[j,l+1],[j,l+2],...[j,r]都不符合条件
            //因为这些区间a[i]是第i-j+1个数同时是第i-j+1小的
            
            //如果a[j]大于a[i],那么就要从r+1开始找哪些数作为右端点是不符合的
            if(a[j]>a[i]){
                l=r+1;
                r=l;
                while(r<n&&a[r+1]>a[i]) r++;
            }
            if(l<=n) d[j][l]++,d[j][r+1]--;
        }
    }
    //最后统计答案
    int ans=0;
    for(int i=1;i<=n;i++){
        int f=0;
        for(int j=i;j<=n;j++){
            f+=d[i][j];
            if(f==0) ans++;
        }
    }
    cout<<ans<<endl;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
        solve();
    return 0;
}

E.Puzzle: Square Jam

就一直贪心最大的正方形,在每次剩下的矩形中,取最大的正方形,这样的话肯定有解,不会出现NO的情况,直到左端点的坐标(x,y),x等于n或者y等于m,此时已经将整个矩形全部取完了

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<set>
#include<deque>
#define endl '\n'
//#define int long long
using namespace std;
typedef long long ll;
int n,m;
struct node {
    int x,y,l;
};
void solve() {
    cin>>n>>m;
    vector<node>ans;
    int x=0,y=0;
    while(x<n&&y<m) {
        int a=n-x,b=m-y;
        if(a<=b) {
            ans.push_back({x,y,a});
            y+=a;
        } else {
            ans.push_back({x,y,b});
            x+=b;
        }
    }
    cout<<"YES"<<endl;
    cout<<ans.size()<<endl;
    for(auto v:ans) cout<<v.x<<" "<<v.y<<" "<<v.l<<endl;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
        solve();
    return 0;
}

 

I.Non-Puzzle: Segment Pair

从贡献度的角度考虑,从1到5e5,枚举每一个点,对于每一个点,我们考虑包含它的所有区间,当然产生答案的区间只由当前点i作为左端点的区间产生,该区间肯定是新枚举到的,所以由它对答案产生贡献的话,不会出现重复的情况

如果枚举到当前区间刚好是n个区间对,那么就可以统计答案,即除去当前枚举到的区间,算之前两条线段都覆盖该点的区间对数量sum,2^sum即为枚举到的当前区间对答案的贡献

AC代码;

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
#include<vector>
#include<cstdio>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N=5e5+10,mod=1e9+7;
vector<int>l[N],r[N];//l[x]放置以x为左端点的区间对下标,r[x]放置以x为右端点的区间对下标
int vis[N];//vis[x]表示第x对区间中有几条线段(0,1,2)覆盖了当前位置
int n;
//快速幂
int qmi(int a,int k){
    int res=1;
    while(k){
        if(k&1) res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        k>>=1;
    }
    return res;
}
void solve() {
    cin>>n;
    for(int i=1;i<=n;i++){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        l[l1].push_back(i),r[r1].push_back(i);
        l[l2].push_back(i),r[r2].push_back(i);
    }
    int res=0;
    int num=0;//覆盖了点i的区间有几个
    int cnt=0;//当前使用了几对区间对
    //从贡献度的角度考虑
    //从1枚举到5e5,对于每一个点i,我们都枚举包含它的所有区间,具体操作是删去前面枚举过的不包含它的区间(即删去右端点为i-1的区间),再加上左端点为i的区间
    //我们统计覆盖某个点的n对区间的选择方式数量,然后全部加起来,注意中间要去重
    for(int i=1;i<=5e5;i++){
        //删去右端点为i-1的区间
        for(auto j:r[i-1]){
            num--;//覆盖点i的区间个数-1
            vis[j]--;//第j对区间中覆盖当前位置i的线段条数-1
            if(!vis[j]) cnt--;//如果第j对区间中覆盖当前位置i的线段条数为0,那么说明当前使用的区间对数量也相应地-1
        }
        //加上左端点为i的区间
        for(auto j:l[i]){
            if(!vis[j]) cnt++;//如果第j对区间是第一次覆盖当前位置,那么当前使用的区间对数量+1
            num++;//覆盖点i的区间个数+1
            vis[j]++;//第j对区间中覆盖当前位置i的线段条数+1
            //如果当前使用的区间对数量刚好等于n,说明点i被n对区间覆盖了,那么就可以统计答案了
            //第j对区间作为枚举的n对区间的最后一对区间,我们需要算在它之前有几对区间是两条线段均覆盖到点i的
            //然后根据乘法原理,有几对区间是两条线段均覆盖到点i的,就乘几个2(由于那些只有一条线段覆盖到点i的是必选的,所以是乘1,乘1可忽略)
            if(cnt==n){
                //假设有x对区间只有一条线段覆盖点i,有y对区间两条线段均覆盖点i
                //那么num=x+2*y,cnt=x+y,所以y=num-cnt
                //由于我们要算的是之前有几对区间是两条线段均覆盖到点i的,所以如果vis[j]为1,那么num-cnt即为在这之前两条线段均覆盖到点i的区间对数量
                //如果vis[j]为2,那么我们算的两条线段均覆盖到点i的区间对数量多加了1,所以要减1,故统一为加一个1-vis[j]
                int sum=num-cnt+1-vis[j];
                res=(res+qmi(2,sum))%mod;
            }
        }
    }
    cout<<res<<endl;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--)
        solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值