目录
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,要求任取一个连续的子序列,使得 取得最大值,可以取空序列。
-
思路:
观察到m比较小,考虑使用dp,用dp[i][j]表示选择的序列以i为右端点,长度%m==j的最大值,可以比较容易得到下面的转移方程,在一些细节判断上要注意。
-
代码:
#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]的转移,另一种同理。
+r[i]-l[i],我们可以知道,dp[i][0]由3个部分组成,之前行的解+上一状态到这一行最右边的距离+这一行必须走的距离。其中关键是上一状态到这一行最右边的距离。
抽象为,分类讨论,有4种情况,
;;;,取一个最小值即可。
(要用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(最小环)
- 题意:
给定一个序列,若,那么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,那么单调递增的排列数为,就可以求出来了。
- 代码:
#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变成(向下取整)的代价为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;
}