银川选拔赛补题

我菜得很抱歉。。

A、查询区间众数出现次数
运用离线查询的思想,莫队算法模版题,可以作为莫队算法板子。代码上有备注~

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <string.h>
#include <map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define pb push_back
#define endl '\n'
using namespace std;
const int maxn=1e5+7;
struct Q{
    int l,r;
    int idx;
}q[maxn];
int pos[maxn];//分组编号
int a[maxn];//查询数组
int ans[maxn];//最终结果记录
bool cmp(Q a,Q b){
    if(pos[a.l]!=pos[b.l]){
        return pos[a.l]<pos[b.l];
    }
    else{
        return a.r<b.r;
    }
}
int res=0;
int numcnt[maxn];//某个数的出现次数
int cntcnt[maxn];//区间内出现某个次数的数的个数
void add(int pos){
    //加入一个元素
    int t=a[pos];
    numcnt[t]++;//当前数的出现次数++
    cntcnt[numcnt[t]]++;//出现次数的次数++
    if(numcnt[t]>res){
        res=numcnt[a[pos]];
    }
}
void sub(int pos){
    int t=a[pos];
    cntcnt[numcnt[t]]--;//被删去的元素的出现次数的出现次数
    if(res==numcnt[t]&&cntcnt[numcnt[t]]==0){
        res--;//如果当前答案的出现次数变成0了,就让它--
    }
    numcnt[t]--;
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].idx=i;
    }
    int siz=sqrt(n);
    for(int i=1;i<=n;i++){
        pos[i]=i/siz;
    }
    sort(q+1,q+1+m,cmp);
    int l=1,r=0;
    for(int i=1;i<=m;i++){
        while(q[i].l<l) add(--l);
        while(q[i].r>r) add(++r);
        while(q[i].l>l) sub(l++);
        while(q[i].r<r) sub(r--);

        ans[q[i].idx]=res;
    }
    for(int i=1;i<=m;i++){
        printf("%d\n",ans[i]);
    }   
}

B、pc玩游戏
wuwuwu这个题是个挺好想的二分。

可以看出,随着查询次数的变多,抽屉合法能放下的娃娃越来越少。
所以考虑二分答案,查询最早在什么位置合法放下的娃娃小于k。(要注意放娃娃不能相邻)

代码实现如下:
(我本来用set做的结果tle了(2000ms左右),用数组排序直接过了而且只跑了300ms左右)

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <string.h>
#include <map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define endl '\n'
using namespace std;
const int maxn=2e5+7;
int a[maxn];
int tp[maxn];
int main(){
   // FAST;
    int n,k,L,m;
    scanf("%d%d%d%d",&n,&k,&L,&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&a[i]);
    }
    int temp1=n;
    int temp2=0;
    if(temp1>=L){
        temp1-=L;
        temp2++;
    }
    temp2+=temp1/(L+1);
    if(temp2<k){
        cout<<0<<endl;
        return 0;
    }
    //二分
    int l=1;
    int r=m;
    int temp=-1;
    while(l<=r){
        int mid=(l+r)>>1;
        tp[0]=0;
        for(int i=1;i<=mid;i++){
            tp[i]=a[i];
        }
        tp[mid+1]=n+1;
        sort(tp,tp+mid+2);
        int putin=0;
        for(int i=1;i<=mid+1;i++){
            int nn=tp[i-1];
            int nexx=tp[i];
            int d=nexx-nn-1;
            if(d>=L){
                d-=L;
                putin++;
                putin+=d/(L+1);
            }
        }
        if(putin<k){
            temp=mid;
            r=mid-1;
        }
        else{
            l=mid+1;
        }
    }
    cout<<temp<<endl;
}

C、pc买礼物

这是个dp,算方案数的dp(之前写过类似的,只不过那个是一维的而已。。),因为方案不同的判定条件为路径不同/携带礼物不同,所以dp数组记录到达的某点编号和当前礼物价值,(因为给出的单向边都是小节点指向大节点,直接按节点编号dp就行,不然还得拓扑序)。

思考一下,只需要记录当前总价值和当前所在节点就可以确定方案数。

代码实现:

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <string.h>
#include <map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define pb push_back
#define endl '\n'
using namespace std;
//在图上dp
const int maxn=2e3+7;
const int mod=998244353;
vector<int> mp[maxn];
ll dp[maxn][maxn];
ll w[maxn];
int main(){
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        cin>>w[i];
    }
    while(m--){
        int a,b;
        cin>>a>>b;
        //a->b有一条有向边
        //只要记录某个点从哪来
        mp[b].pb(a);
    }
    dp[1][0]=1;
    dp[1][w[1]]=1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<mp[i].size();j++){
            int t=mp[i][j];
            for(int x=0;x<=k;x++){
                dp[i][x]=(dp[i][x]+dp[t][x])%mod;
                if(x-w[i]>=0){
                    dp[i][x]=(dp[i][x]+dp[t][x-w[i]])%mod;
                }
            }
        }
    }
    ll ans=0;
    for(int i=0;i<=k;i++){
        ans=(ans+dp[n][i])%mod;
    }
    cout<<ans<<endl;
}

D、game
sb思维题,直接判断奇偶,就不放代码了。

E、median
这也是水题。
比赛的时候没看懂这“排列”的意思。。。。。原来v只会出现一次。。b瞎写了好久,绝了,一开始第一眼的思路就是对的。

因为要形成一个v是中位数的数组,那么数组中大于v和小于v的数量必须相等

直接在数组中找到v的位置,看v之前任一段序列中有多少大于v的,多少小于v的,记录每个点到v形成的序列大于v和小于v的个数的差值,然后记录这个差值出现的次数。(别忘了前后各自有个位置是0,0)
v之后的序列一样处理。

然后前后组合就好了。

代码实现:

#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <string.h>
#include <map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define endl '\n'
using namespace std;

const int maxn=1e5+7;
int a[maxn];
map<int,ll> mp1,mp2;//分别记录前后缀差值信息
int main(){
    int n,v;
    scanf("%d%d",&n,&v);
    int i0=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        if(a[i]==v){
            i0=i;
        }
    }
    int cntb=0;
    int cnts=0;
    mp1[0]++;
    mp2[0]++;
    for(int i=i0-1;i>=1;i--){
        if(a[i]<v){
            cnts++;
        }
        else{
            cntb++;
        }
        mp1[cnts-cntb]++;
    }
    cntb=0;
    cnts=0;

    for(int i=i0+1;i<=n;i++){
        if(a[i]<v){
            cnts++;
        }
        else{
            cntb++;
        }
        mp2[cnts-cntb]++;
    }
    ll ans=0;
    for(int i=0;i<=n;i++){
        //cout<<mp1[i]<<' '<<mp[-i]<<endl;
        ans+=mp1[-i]*mp2[i];
        if(i!=0)
            ans+=mp1[i]*mp2[-i];
    }
    printf("%lld\n",ans);
}

F、重建网络

这个题挺有意思的,开始和学长讨论还以为这个是个假题,后来看了题解的解释,才发现这个题的妙处。

思路就是对给出的无向图跑一次最大生成树,如果用到的边有小于k的,那么答案sum(小于k的且用到的边-k)。
如果用到的边都大于k,答案就是所有的边与k最小的差值。(为什么呢?)
通过思考可以发现,当前已经形成树了,如果与k差值最小的那个边不在树上,可以直接替换上去。(删去换上去的边两节点(a,b)任意点与集合相连的那个边即可)

(非主流最大生成树写法。。。堆优化prim,边权全部取负数)

代码实现:

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <string.h>
#include <map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define pb push_back
#define endl '\n'
using namespace std;

const int maxn=1e5+7;
const int maxm=2e5+7;

struct edge{
    int to;
    ll w;
};
vector<edge> mp[maxn];

typedef pair<ll,int> P;//first为距离,second为编号

bool vis[maxn];
ll dis[maxn];

int n,m;
ll k;
ll cntedge[maxn];
ll ed[maxm];

int cnt=0;

void prim(){
    //最大生成树
    priority_queue<P,vector<P>,greater<P> >q;
    q.push({0,1});//随便让一个点入队;
    while(q.size()){
        P t=q.top();
        q.pop();
        ll w0=t.first;
        ll v0=t.second;
        //cout<<"w0="<<w0<<endl;
        if(vis[v0]) continue;
        vis[v0]=1;
        if(w0!=0)
            cntedge[++cnt]=-w0;
        for(int i=0;i<mp[v0].size();i++){
            edge e=mp[v0][i];
            q.push({e.w,e.to});
        }
    }
    
}

int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++){
        int a,b;
        ll c;
        cin>>a>>b>>c;
        mp[a].pb({b,-c});//建图用负边权
        mp[b].pb({a,-c});
        ed[i]=c;
    }
    prim();
    ll ans=0;
    int flag=0;
    ll minn=1e15;
    for(int i=1;i<=cnt;i++){
        //cout<<"cntedge="<<cntedge[i]<<endl;
        if(cntedge[i]<k){
            flag=1;
            ans+=abs(k-cntedge[i]);
            //cout<<"abs="<<abs(k-cntedge[i])<<endl;;
        }
    }
    
    //cout<<"flag="<<flag<<endl;
    if(flag==1){
        cout<<ans<<endl;
    }
    else{
        for(int i=1;i<=m;i++){
            minn=min(minn,abs(k-ed[i]));
        }
        cout<<minn<<endl;
    }
}

G 、最大得分
这是个动态规划的题。

看了题解,想了好久8久,问了半天才做出来。。
wuwuwu

直接上代码吧,上面有注释o(`ω´ )o

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <string.h>
#include <map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define pb push_back
#define endl '\n'
using namespace std;
const int maxn=1e4+7;
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int a[maxn];
//三维dp数组,备注依次为dp数组三个参数的意义和dp数组记录的数值的意义
int dp[maxn][55][110];//已经分好了数组a中的第几个数,当前在分哪个部分,当前部分的gcd为,dp数组记录已经分好了的部分的sum
int main(){
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    memset(dp,-1,sizeof dp);//达不到的状态标记为-1
    dp[0][0][0]=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<=k;j++){
            for(int w=0;w<=100;w++){
                if(dp[i][j][w]<0) continue;//这个状态达不到
                dp[i+1][j+1][a[i+1]]=max(dp[i+1][j+1][a[i+1]],dp[i][j][w]+w);//下一个数字分到下一个部分去
                if(j>0){
                    //下一个数字直接放在这个部分
                    dp[i+1][j][gcd(a[i+1],w)]=max(dp[i+1][j][gcd(a[i+1],w)],dp[i][j][w]);
                }
            }
        }
    }
//    for(int i=0;i<n;i++){
//        for(int j=0;j<=k;j++){
//            for(int w=1;w<=100;w++){
//                cout<<dp[i][j][w]<<endl;
//            }
//        }
//    }
//老debug怪了
    //寻找最大的答案
    int ans=0;
    for(int i=0;i<=100;i++){
        //必须要能达到这个状态才可以计算答案
        if(dp[n][k][i]!=-1)
            ans=max(ans,dp[n][k][i]+i);
    }
    cout<<ans<<endl;
}

H、pc要出题

对不起对不起对不起我以为1e7数组会爆硬是用了map查询,然后把我tle到傻,又被容器坑了一把。
问了大佬同学,他说如果map用下标查询原本不存在的节点,会现场建一个节点,时间成本较大,所以建议用find查询map。
要两个数的和能除尽k,那么就是两个数取模和等于k,就是个sb题。
(这里分享两种写法)

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <string.h>
#include <map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define endl '\n'
using namespace std;
ll cnt[10000007];
int a[1000007];
bool vis[100000007];
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    ll ans=0;
    for(int i=1;i<=n;i++){
        ll temp;
        scanf("%lld",&temp);
        temp%=k;
        //a[i]=k;
        cnt[temp]++;
    }
    for(int i=0;i<k;i++){
        if(vis[i]==1) continue;
        if(i==0|k-i==i){
        //记得特判
            ans+=cnt[i]*(cnt[i]-1)/2;
            vis[i]=1;
        }
        else{
            ans+=cnt[i]*cnt[k-i];
            vis[i]=1;
            vis[k-i]=i;
        }
    }
    printf("%lld\n",ans);
}

int cnt[10000007];
int a[1000007];
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    ll temp;
    ll ans=0;
    while(n--){
        scanf("%lld",&temp);
        temp%=k;
        if(temp==0){
            ans+=cnt[0];
        }
        else{
            ans+=cnt[k-temp];
        }
        cnt[temp]++;
    }
    printf("%lld\n",ans);
    return 0;
}

完结撒花~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KaaaterinaX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值