CF1941 div3题解

CF1941 div3题解

暑假集训个人赛打破防了,怒刷div3找自信

E题一眼单调队列,但是因为没看完题 a i , j + 1 a_{i,j}+1 ai,j+1样例不对,莫名开始怀疑自己的dp推错了,唐完了

A

O ( n m ) O(nm) O(nm)枚举,没什么好说的

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

void work()
{
    int n,m,k;
    cin>>n>>m>>k;
    vi b(n),c(m);
    rep(i,0,n) cin>>b[i];
    rep(i,0,m) cin>>c[i];
    int ans=0;
    rep(i,0,n)
        rep(j,0,m)
            if(b[i]+c[j]<=k) ans++;
    cout<<ans<<'\n';
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

B

从左到右模拟一遍即可,但是我忘记判断倒数第二位也要为WA了一发

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

void work()
{
    int n;
    cin>>n;
    vi a(n);
    rep(i,0,n)
        cin>>a[i];
    rep(i,1,n-1)
    {
        if(a[i]<a[i-1]*2||a[i+1]<a[i-1]) {cout<<"NO\n";return;}
        else
        {
            a[i]-=a[i-1]*2;
            a[i+1]-=a[i-1];
        }
    }
    if(a[n-1]==0&&a[n-2]==0) cout<<"YES\n";
    else cout<<"NO\n";

}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

C

又是没读完题,一开始以为这里substring的定义是不连续的,想了半天

In formal language theory and computer science, a substring is a contiguous sequence of characters within a string

那么显然对于map和pie去掉中间的字母一定比去掉两边更好,特判一下mapie这种情况就可以了

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

void work()
{
    int n;
    cin>>n;
    string s;
    vector<bool> vis(n,false);
    cin>>s;
    int ans=0;
    rep(i,0,n)
    {
        if(s[i]=='m')
        {
            if(i+4<n)
            {
                if(s[i+1]=='a'&&s[i+2]=='p'&&s[i+3]=='i'&&s[i+4]=='e') {ans++,vis[i+2]=true;continue;}
            }
            if(i+2<n)
                if(!vis[i+2]&&s[i+1]=='a'&&s[i+2]=='p') ans++;
        }
        if(s[i]=='p'&&!vis[i])
        {
            if(i+2<n)
                if(s[i+1]=='i'&&s[i+2]=='e') ans++;
        }
    }
    cout<<ans<<'\n';
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

D

n n n个人站一圈传球,传 m m m轮,告诉你每一轮的传球的距离和可能的方向,顺时针或逆时针或不确定,输出最后可能被传到的人的编号

1 ≤ n ≤ 1000 , 1 ≤ m ≤ 1000 1 \le n \le 1000,1 \le m \le 1000 1n1000,1m1000

第一分钟想到是一颗二叉树,又一想发现会重复,因为至多只有 1000 1000 1000​个人,那么我们直接开两个set模拟一下就可以

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

void work()
{
    int n,m,x;
    cin>>n>>m>>x;
    vector<int> di(m+1),op(m+1);
    rep(i,1,m)
    {
        char ch;
        cin>>di[i]>>ch;
        if(ch=='?') op[i]=2;
        if(ch=='0') op[i]=0;
        if(ch=='1') op[i]=1;
    }
    set<int> S,nS;
    S.insert(x-1);
    rep(i,1,m)
    {
        trav(j,S)
        {
            if(op[i]==0)
            {
                nS.insert((j+di[i])%n);
            }
            if(op[i]==1)
            {
                nS.insert((j-di[i]+n)%n);
            }
            if(op[i]==2)
            {
                nS.insert((j+di[i])%n);
                nS.insert((j-di[i]+n)%n);
            }
        }
        S=nS;
        nS.clear();
    }
    cout<<sz(S)<<'\n';
    trav(i,S)
        cout<<i+1<<' ';
    cout<<'\n';
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

E

给一个 n ∗ m n*m nm的矩阵 a i , j a_{i,j} ai,j表示水深,建造一座桥的要求如下

1.在 a i , j a_{i,j} ai,j格子下造桥墩的代价是 a i , j + 1 a_{i,j}+1 ai,j+1

2.桥是从 ( i , 1 ) (i,1) (i,1) ( i , m ) (i,m) (i,m)的,一座桥的所有桥墩之间的距离不超过 d d d,起点和终点一定要建造桥墩

问在连续 k k k行造 k k k个桥的最小代价是多少,不同桥的桥墩之间互不影响

写这个简单优化dp写了1个小时,耻辱,回忆心路历程

每一行之间相互独立,那我们单独考虑每一行

1、看完题想到设 d p [ i ] dp[i] dp[i]为在第 i i i个格子放桥墩,且前 i i i个格子稳定的最小代价,那么显然就是

d p [ i ] = m i n ( d p [ j ] ) + a [ i ] , ( i − j − 1 ≤ d 且 1 ≤ j ) dp[i]=min(dp[j])+a[i],(i-j-1 \le d且1 \le j) dp[i]=min(dp[j])+a[i],(ij1d1j)

非常经典的可以用数据结构优化的 d p dp dp方程,可以单调队列也可以线段树也可以用堆(用之前一直弹出过期的堆头就可以)

2、直接写单调队列,敲完之后一看,样例每个点的答案都不对。然后就想

欸,我是不是没考虑前面不放桥墩的情况啊

然后就改成了 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1],推了个一眼觉得很正确的式子开始重新写,发现答案又不对。

发现……放桥墩的情况你不能从不放桥墩转移过来,那这个式子不就跟一开始推得一样?

3、找了半天,最终决定再看一遍题,好家伙, a i , j + 1 a_{i,j}+1 ai,j+1,加上1之后也是飞快地过了

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const ll inf=0x3f3f3f3f3f3f3f3f;

void work()
{
    int n,m,k,d;
    cin>>n>>m>>k>>d;
    vector<ll> dp(m+1);
    vector<ll> sum(n+1,0);
    vector<vector<int> > a(n+1,vector<int> (m+1,0));
    rep(i,1,n)
        rep(j,1,m)
            cin>>a[i][j];
    sum[0]=0;
    rep(i,1,n)
    {
        rep(j,1,m) dp[j]=inf;
        dp[1]=1;
        deque<int> q;
        q.push_back(1);
        rep(j,2,m)
        {
            /*
            rep(k,0,1)
                {while(!q[k].empty()&&j-q[k].front()-1>d) q[k].pop_front();}

            dp[j][0]=dp[q[1].front()][1];
            dp[j][1]=min(dp[q[0].front()][0],dp[q[1].front()][1])+a[i][j];
            printf("q[0].front=%d,q[1].front=%d,dp[%d][0]=%lld,dp[%d][1]=%lld\n"
                   ,q[0].front(),q[1].front(),j,dp[j][0],j,dp[j][1]);
            rep(k,0,1)
            {
                while(!q[k].empty()&&dp[j][k]<dp[q[k].back()][k]) q[k].pop_back();
                q[k].push_back(j);
            }
            */
            while(!q.empty()&&j-q.front()-1>d) q.pop_front();
            dp[j]=dp[q.front()]+a[i][j]+1;
            //printf("q.front=%d,dp[%d]=%lld\n",q.front(),j,dp[j]);
            while(!q.empty()&&dp[j]<dp[q.back()]) q.pop_back();
            q.push_back(j);
        }
        //cout<<'\n';
        sum[i]=sum[i-1]+dp[m];
    }

    ll ans=inf;
    rep(i,1,n)
    {
        if(i+k-1<=n) ans=min(ans,sum[i+k-1]-sum[i-1]);
    }
    cout<<ans<<'\n';
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

F

给定数组 a , d , f a,d,f a,d,f,长度分别为 n , m , k n,m,k n,m,k a a a有序,最多向 a a a中插入一个数 d i + f j d_i+f_j di+fj,且 a a a仍然有序,问操作后最大的 a i + 1 − a i a_{i+1}-a_i ai+1ai为多少

$1 \le n,m,k \le 2e5 $​

写这题的时候也很唐,还是太菜了

显然我们只需要考虑初始最大值和次大值,如果有多个最大值显然不可能改变答案

然后考虑在最大值之间插入一个值,我一开始的想法是 a p a_p ap a p + 1 a_{p+1} ap+1(设他们两个之间是最大值),中间有一个区间是可以使得答案更小的

但是这样也是 O ( m a x ( a i ) m ) O(max(a_i)m) O(max(ai)m)

过了十多分钟才想起来,肯定越靠近中点的越优啊,那么二分就完事了

一个div3能有多难呢

然后就是学到了强制类型转换不能写在括号外面,WA了一发是因为

(ll)(a[mi+1]+a[mi])/2

写在这里会先加,已经爆成负数了再转成long long,应该写在加数前面先转一个变成long long

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

void work()
{
    int n,m,k;
    cin>>n>>m>>k;
    vi a(n),d(m),f(k);

    rep(i,0,n) cin>>a[i];
    rep(i,0,m) cin>>d[i];
    rep(i,0,k) cin>>f[i];
    sort(f.begin(),f.end());
    sort(d.begin(),d.end());
    int mx=-1,mi=-1,mx1=-1;
    rep(i,0,n-1)
    {
        if(a[i+1]-a[i]>mx)
        {
            mx1=mx;
            mx=a[i+1]-a[i],mi=i;
        }
        else
        {
            if(a[i+1]-a[i]>mx1) mx1=a[i+1]-a[i];
        }
    }
    rep(i,0,n-1)
        if(mx==a[i+1]-a[i]&&mi!=i) {cout<<mx<<'\n';return;}

    int ans=mx;
    rep(i,0,m)
    {
        int l=0,r=k-1;
        while(l<r)
        {
            int mid=l+r+1>>1;
            if(d[i]+f[mid]<=((ll)a[mi+1]+a[mi])/2) l=mid;
            else r=mid-1;
        }
        int res1=d[i]+f[l];
        if(a[mi]<=res1&&res1<=a[mi+1])
            ans=min(ans,max(res1-a[mi],a[mi+1]-res1));
        if(l+1<k)
        {
            int res2=d[i]+f[l+1];
            if(a[mi]<=res2&&res2<=a[mi+1])
                ans=min(ans,max(res2-a[mi],a[mi+1]-res2));
        }
    }
    cout<<max(ans,mx1)<<'\n';
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

G

简单最短路,怎么建图一眼可以看出来

喜欢用vector但是开数组大小的时候开的 2 n 2n 2n没有开 n + m n+m n+m搞得WA了很多发还不明白

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=2e5+5;
const int inf=0x3f3f3f3f;
struct Edge
{
    int to;int dis;
};
vector<Edge> adj[maxn*2];
vi dis;
map<int,int> f;
struct node
{
    int u;int val;
};
bool operator<(const node &a,const node &b)
{
    return a.val>b.val;
}
int be,ed;
int n,m,cnt;
void dijkstra()
{
    rep(i,1,n+m) dis[i]=inf;
    priority_queue<node> q;
    q.push((node){be,0});dis[be]=0;
    while(!q.empty())
    {
        node tmp=q.top();q.pop();
        int u=tmp.u,d=tmp.val;
        if(dis[u]!=d) continue;
        trav(e,adj[u])
        {
            int v=e.to;
            if(dis[u]+e.dis<dis[v])
            {
                dis[v]=dis[u]+e.dis;
                q.push((node){v,dis[v]});
            }
        }
    }
}
void init()
{
    rep(i,1,n+m) adj[i].clear();
    dis.clear();f.clear();
    dis.resize(n+m+1);cnt=n;
}
void work()
{
    cin>>n>>m;
    init();
    rep(i,1,m)
    {
        int x,y,z;
        cin>>x>>y>>z;
        if(!f[z]) f[z]=++cnt;
        adj[x].push_back((Edge){f[z],0});
        adj[y].push_back((Edge){f[z],0});
        adj[f[z]].push_back((Edge){x,1});
        adj[f[z]].push_back((Edge){y,1});
    }
    cin>>be>>ed;
    dijkstra();
    cout<<dis[ed]<<'\n';
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值