Codeforces 刷题一

 

目录

CF1197D(dp)

题意:

思路:

代码:

CF1201D(dp)

题意:

思路:

   - 代码:

CF1187(换根dp)

扫描二维码关注公众号,回复: 11592424 查看本文章

- 题意:

- 思路:

- 代码:

CF 1205B Shortest Cycle(最小环)

- 题意:

- 思路:

- 代码:

CF 1204D Kirk and a Binary String(思维)

- 题意:

- 思路:

- 代码:

CF 1207D Number Of Permutations(排列 容斥)

- 题意:

- 思路:

- 代码:

CF 1207E XOR Guessing(交互题 位运算)

- 题意:

- 思路:

- 代码:

CF 1213D Equalizing by Division(思维+优先队列)

- 题意:

- 思路:

- 代码:


CF1197D(dp)

  • 题意:

给定一个序列{ai},m和k,要求任取一个连续的子序列a_l,a_l_+_1,...,a_r,使得 \sum_{i=l}^{r}a_i -k*\lceil(r-l+1)/m\rceil 取得最大值,可以取空序列。(1\leq n \leq 3e5 , 1 \leq m \leq 10 , 1 \leq k \leq 1e9)

  • 思路:

观察到m比较小,考虑使用dp,用dp[i][j]表示选择的序列以i为右端点,长度%m==j的最大值,可以比较容易得到下面的转移方程,在一些细节判断上要注意。

dp[i][j]=\left\{\begin{matrix} & max(dp[i-1][0]+a[i]-k,a[i]-k)\quad if(j==0 \or\ m==1 )\\ & dp[i-1][m-1]+a[i]\quad\ if(j==0) \\ & dp[i-1][j-1]+a[i] \quad else \end{matrix}\right.

  • 代码:

#include<bits/stdc++.h>
#define ll long long
#define inf 1e18
using namespace std;
const int maxn=3e5+10;
int n,m;
ll k;
ll dp[maxn][20];
ll arr[maxn];
int main()
{
    scanf("%d%d%lld",&n,&m,&k);
    for(int i=1;i<=n;i++)scanf("%lld",&arr[i]);
    for(int i=0;i<=n;i++)
    for(int j=0;j<m;j++)dp[i][j]=-inf;//初始化
    ll ans=0;
    for(int i=1;i<=n;i++)
    for(int j=0;j<m;j++){
        if(j==1||m==1){
            dp[i][j]=max(dp[i-1][0]+arr[i]-k,arr[i]-k);
        }
        else if(j==0)dp[i][j]=dp[i-1][m-1]+arr[i];
        else dp[i][j]=dp[i-1][j-1]+arr[i];
        ans=max(ans,dp[i][j]);
    }
    printf("%lld\n",ans);
}

CF1201D(dp)

  • 题意:

给一个地图,有一些宝藏,可以随意左右走,不能往下走,给定一些safe列,在上面可以往上走,问走遍所有宝藏的最小移动距离。

  • 思路:

首先处理出每一行的最左边和最右边的宝藏l[i],r[i],每一列的左边最接近的safe列pl[i],pr[i]。不能往下走,故一行行处理,考虑dp。设dp[i][0/1]表示从遍历1-n的宝藏并且在第i行的最左边/最右边的最小移动距离。先考虑dp[i][0]的转移,另一种同理。

dp[i][0]= min(dp[pre][0]+dis(pre,pl[pre],i,r[i]),dp[pre[0]+dis(pre,pr[pre],i,r[i]))+r[i]-l[i],我们可以知道,dp[i][0]由3个部分组成,之前行的解+上一状态到这一行最右边的距离+这一行必须走的距离。其中关键是上一状态到这一行最右边的距离。

抽象为(x_1,y_1)\rightarrow (x_2,y_2),分类讨论,有4种情况,

y_1\rightarrow pl[y_1]\rightarrow y_2y_1\rightarrow pr[y_1]\rightarrow y_2y_1\rightarrow pl[y_2]\rightarrow y_2y_1\rightarrow pr[y_2]\rightarrow y_2,取一个最小值即可。

(要用ll)

   - 代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+10;
ll n,m,k,q;
ll lie[maxn];//safe列
ll l[maxn],r[maxn];//每行最左和最优的宝藏
ll pl[maxn],pr[maxn];//每列左边和右边的safe列
ll dp[maxn][2];
ll cl(ll x,ll y,ll z){
    return abs(x-z)+abs(y-z);//水平移动的和
}
ll dis(ll x1,ll y1,ll x2,ll y2){
    int disx=(x2-x1);//往上走了几步
    int disy=min(min(cl(y1,y2,pl[y1]),cl(y1,y2,pl[y2])),min(cl(y1,y2,pr[y1]),cl(y1,y2,pr[y2])));//分类讨论
    return disx+disy;
}
int main()
{
    scanf("%lld%lld%lld%lld",&n,&m,&k,&q);
    for(ll i=1,a,b;i<=k;i++){
        scanf("%lld%lld",&a,&b);
        if(l[a]==0)l[a]=b,r[a]=b;
        else l[a]=min(l[a],b),r[a]=max(r[a],b);
    }
    for(ll i=1;i<=q;i++)scanf("%lld",&lie[i]);sort(lie+1,lie+1+q);lie[0]=-1e18,lie[q+1]=1e18;
    for(ll i=1;i<=m;i++){//找到每一行的左边和右边最近的safe列
        pl[i]=lie[lower_bound(lie+1,lie+1+q,i)-lie-1];
        pr[i]=lie[lower_bound(lie+1,lie+1+q,i)-lie];
    }
    ll ans=0;
    if(l[1]==0){//对第一行进行特殊处理
        dp[1][0]=dp[1][1]=0;
        l[1]=r[1]=1;
    }
    else{
        dp[1][0]=dp[1][1]=r[1]-1;
        l[1]=r[1];
    }
    ll pre=1;
    for(ll i=2;i<=n;i++){
        if(l[i]!=0){
            ll len1,len2,len3,len4;
            len1=dp[pre][0]+dis(pre,l[pre],i,l[i]);//左到左
            len2=dp[pre][1]+dis(pre,r[pre],i,l[i]);//右到左
            len3=dp[pre][0]+dis(pre,l[pre],i,r[i]);//左到右
            len4=dp[pre][1]+dis(pre,r[pre],i,r[i]);//右到右
            dp[i][0]=min(len3,len4)+(r[i]-l[i]);//更新
            dp[i][1]=min(len1,len2)+(r[i]-l[i]);//(r[i]-l[i])是i行必须要走的距离
            pre=i;
        }
    }
    cout<<min(dp[pre][0],dp[pre][1])<<endl;
}

CF1187(换根dp)

- 题意:

给定一棵树,问选择任意树根root形成的所有节点的子树之和最大值是多少。

- 思路:

一开始想找规律,结果wa9了,后来才知道有换根dp这个东西,学了之后就简单了,套个板子就行。记得ll。

- 代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=4e5+10;
struct edge{
    int to,next;
}e[maxn*2];
int head[maxn],cnt;
void add(int u,int v){
    e[cnt].to=v;e[cnt].next=head[u];head[u]=cnt++;
}
ll tot[maxn],dp[maxn];
ll dfs1(int u,int f){//计算每个点的子树个数
    tot[u]=1;
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;
        if(v==f)continue;
        tot[u]+=dfs1(v,u);
    }
    return tot[u];
}
ll dfs2(int u,int f){//先计算以1为根的答案
    dp[u]=tot[u];
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;
        if(v==f)continue;
        dp[u]+=dfs2(v,u);
    }
    return dp[u];
}
ll ans=0;
void dfs3(int u,int f){//换根
    ans=max(ans,dp[u]);
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;
        if(v==f)continue;
        dp[u]-=dp[v];//减掉子树v的贡献
        dp[u]-=tot[v];//减掉u在v的贡献
        tot[u]-=tot[v];//更新子树大小;
        tot[v]+=tot[u];
        dp[v]+=dp[u];
        dp[v]+=tot[u];
        dfs3(v,u);
        //还原
        dp[v]-=dp[u];
        dp[v]-=tot[u];
        tot[v]-=tot[u];
        tot[u]+=tot[v];
        dp[u]+=dp[v];
        dp[u]+=tot[v];
    }
}
int n;
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(int i=1,a,b;i<n;i++)scanf("%d%d",&a,&b),add(a,b),add(b,a);
    dfs1(1,0);dfs2(1,0),dfs3(1,0);
    printf("%lld\n",ans);
}

CF 1205B Shortest Cycle(最小环)

- 题意:

给定一个序列,若a_i \& a_j \neq 0,那么i-j连一条边,问最小的环为多长,环的长度要大于2,没有输出-1,0<=ai<=1e18,n<=1e5

- 思路:

一开始想了挺久,后来发现,如果超过3*64个数字,那么必然有一位超过3个数字为1,那么答案就是3了,那么考虑小于3*63个数字,只要用floyd求一个无向图的最小环即可,随便学习了一下板子。

- 代码:

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
const int N=1e5+10;
ll a[N];
vector<int>e[70];
int n;
int vis[N],ans,ins[N];int dp[200];
/*
void dfs(int x,int sum){
    dp[x]=sum;
    vis[x]=1;
    for(int i=0;i<=63;i++){
        if((a[x]>>i)&1){
            for(int v:e[i]){
                if(!vis[v])dfs(v,sum+1);
                else{
                    if(dp[x]-dp[v]+1>=3)ans=min(ans,dp[x]-dp[v]+1);
                }
            }
        }
    }
}
*/
int dis[300][300],temp[300][300];
int main(){
    scanf("%d",&n);int cnt=0;
    for(int i=1;i<=n;i++){
        ll x;scanf("%lld",&x);if(x==0)continue;
        a[++cnt]=x;
        for(int j=0;j<=63;j++){
            if((x>>j)&1)e[j].push_back(cnt);
        }
    }
    n=cnt;
    for(int i=0;i<=63;i++){
        if(e[i].size()>=3 ){
            cout<<3<<endl;
            return 0;
        }
    }
    ans=inf;
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dis[i][j]=inf/2;
    for(int i=0;i<=63;i++){
        for(int u:e[i]){
            for(int j=0;j<=63;j++){
                if((a[u]>>j)&1){
                    for(int v:e[j]){
                        if(v!=u)dis[u][v]=1;
                    }
                }
            }
        }
    }
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)temp[i][j]=dis[i][j];
    for(int k=1;k<=n;k++){
        for(int i=1;i<k;i++)
        for(int j=i+1;j<k;j++){
            ans=min(ans,dis[i][j]+temp[i][k]+temp[j][k]);
        }
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    }
    if(ans!=inf&&ans<=n)cout<<ans<<endl;
    else cout<<-1<<endl;
}

CF 1204D Kirk and a Binary String(思维)

- 题意:

给定一个01串,让我们构造一个相同长度的01串,满足对于任意的子区间的最长不下降子序列的长度不变,并且有尽可能多的1.

- 思路:

首先,由于要尽可能多的0,我们考虑对于原串的0不变,对于原串的1进行改变,考虑什么时候可以改变。

从后往前,假设当前完成到第i个位置,为1,那么以i为左端点的所有区间,区间Q设为[i,r],如果Q的LIS包含i,那么我们将i改为0则不改变LIS,相当于把11111变成01111还是5。如果Q的LIS不包含i,那么说明LIS是以0开头,我们将1改变为0,就会增大LIS的长度,所以只要判断当前的区间[i,r]能否满足以1开头的LIS即可,并且我们以贪心的策略,能改变则改变。

- 代码:

#include<bits/stdc++.h>
using namespace std;
string s;
int main()
{
    cin>>s;
    int len=s.length();int num0=0,num1=0;
    for(int i=len-1;i>=0;i--){
        if(s[i]=='0')num0++;
        else{
            if(num1>=num0)s[i]='0';
            else num1++;
        }
    }
    cout<<s<<endl;
}

CF 1207D Number Of Permutations(排列 容斥)

- 题意:

给定n的数对(ai,bi),问有几种排列顺序使得ai和bi都不是单调递增的。

- 思路:

我们可以反过来求,n!减去(使得ai或者bi单调递增)由于有重复的数字,我们可以对a和b分开求排列数,但是由于可能会重复求,要再加上。对于ai,如果每个数字出现次数为gsi,那么单调递增的排列数为sum=\prod _{i=1}^{n}(gs_i)!,就可以求出来了。

- 代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int N=3e5+10;
const int mod=998244353;
int n;
ll p[N];
ll gs[N],gs1[N];
void cl(){
    p[0]=1;
    for(int i=1;i<=n;i++)p[i]=(p[i-1]*i)%mod;
}
pair<int,int>P[N];
bool cmp(pair<int,int>x,pair<int,int>y){
    if(x.fi!=y.fi)return x.fi<y.fi;
    else return x.se<y.se;
}
int main()
{
    scanf("%d",&n);
    cl();
    ll ans=p[n];
    for(int i=1,x,y;i<=n;i++){
        scanf("%d%d",&x,&y);P[i].fi=x,P[i].se=y;gs[x]++,gs1[y]++;
    }
    ll ans1=1,ans2=1,ans3=1;
    for(int i=1;i<=n;i++)if(gs[i])ans1=(ans1*p[gs[i]])%mod;
    for(int i=1;i<=n;i++)if(gs1[i])ans3=(ans3*p[gs1[i]])%mod;
    sort(P+1,P+n+1,cmp);
    int flag=0;
    for(int i=1;i<n&&!flag;i++){
        if(P[i].se>P[i+1].se)flag=1;
    }
    int t1=P[1].fi,t2=P[1].se;ll cnt=0;
    for(int i=1;i<=n;i++){
        if(t1==P[i].fi&&t2==P[i].se)cnt++;
        else{
            if(cnt>1)ans2=(ans2*p[cnt])%mod;
            t1=P[i].fi;t2=P[i].se;
            cnt=1;
        }
    }
    ans2=(ans2*p[cnt])%mod;
    if(flag)ans2=0;
    //cout<<ans<<" "<<ans1<<" "<<ans3<<" "<<ans2<<endl;
    cout<<(ans-ans1-ans3+ans2+2*mod)%mod<<endl;
}

CF 1207E XOR Guessing(交互题 位运算)

- 题意:

要求得到一个x的值,我们可以询问两次,每次询问100个数ai,(1<=ai<=2^(14)-1),会返回一个x1,x1=x^(ai)(1<=i<=100),是遂渐选择的。

- 思路:

我们可以一开始询问1-100,那么我们知道1-100的二进制第7位-13为0,那么x1的7-13位就是x的7-13位,现在我们只要求0-6位即可,那么可以构造100个0-6位为0的数字,那么也可以同理得到0-6,从而求出x。

- 代码:

#include<bits/stdc++.h>
using namespace std;

int by[200];
int main()
{
    printf("?");
    for(int i=1;i<=100;i++)printf(" %d",i);
    int t1;
    puts("");
    fflush(stdout);
    scanf("%d",&t1);
    int ans=0;
    ans=(t1>>7)<<7;
    //cout<<ans<<endl;
    int cnt=0;
    for(int i=7;i<14;i++){
        by[cnt++]=(1<<i);
    }
    printf("?");int tot=0;
    for(int i=0;i<(1<<cnt);i++){
        int temp=0;
        for(int j=0;j<cnt;j++){
            if((i>>j)&1)temp+=by[j];
        }
        printf(" %d",temp);tot++;
        if(tot==100)break;
    }
    puts("");
    fflush(stdout);
    int t2;
    scanf("%d",&t2);
    for(int i=0;i<7;i++){
        ans|=(((t2>>i)&1)<<i);
    }
    printf("! %d",ans);

}

CF 1213D Equalizing by Division(思维+优先队列)

- 题意:

给定一个序列,每次使一个数字ai变成ai/2(向下取整)的代价为1,问使得序列至少k个数字相同最小需要多少代价。

- 思路:

比赛的时候想了很久,后来才发现,每一个数字可以变成的数字只有logn个,那我们把这些可以变成的数字nlogn个数字放到优先队列中,代价为值,数字为下标,每一个下标维护k小值即可。

- 代码:

#include <bits/stdc++.h>
#define  ll long long
using namespace std;
const int N=2e5+10;
ll sum[N];
int a[N];
priority_queue<int> q[N];
int n,k;
int main()
{
	ll ans=1e18;
	scanf("%d%d",&n,&k);
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		int t=0;
		while(x!=0){
			//cout<<x<<endl;
			if(q[x].size()<k)q[x].push(t),sum[x]+=t;
			else{
				if(t>=q[x].top());
				else{
					sum[x]-=q[x].top();q[x].pop();
					sum[x]+=t;q[x].push(t);
				}
			}
			t++;x/=2;
		}
	}
	for(int i=1;i<=200000;i++){
		if(q[i].size()>=k){
			ans=min(ans,sum[i]);
		}
	}
	cout<<ans<<endl;
}

猜你喜欢

转载自blog.csdn.net/qq_40400202/article/details/99656251