银川选拔赛补题

我菜得很抱歉。。

A

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

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

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 sb思维题,直接判断奇偶,就不放代码了。

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

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

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

然后前后组合就好了。

代码实现:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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<
        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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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="<
        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="<
        if(cntedge[i]<k){
     
            flag=1;
            ans+=abs(k-cntedge[i]);
            //cout<<"abs="<
        }
    }
    
    //cout<<"flag="<
    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

H pc要出题

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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;
}

(还有一个莫队一个dp没写,写完马上补上题解)

你可能感兴趣的:(刷题题,算法,acm竞赛)