8.17模拟赛题解

在这里插入图片描述
先考虑空间能不能把N个座位放好 最优的方式就是挨着摆放
那么一排能摆放Q=L/x的商个椅子 ,然后计算摆放完N个座位需要多少排,N/Q 向上取整
计算所需要的排总共占据多宽,讨论有没有超过W,然后讨论剩余空间还能放几条走廊
如果走廊数量为0,那么无解。最后一条走廊理论上可以挨着两排位置,所以说只需要计算理论上最多能有多少座位挨着走廊,然后跟实际座位数取min即可 ,注意一排可能一个座位也放不了,要讨论

#include <bits/stdc++.h>
#define FIN freopen("in.txt", "r", stdin)
#define FOUT freopen("output/out.txt", "w", stdout)
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
typedef long double ld;

int n,l,w,x,y,a;

int main()
{
   // freopen("aisle.in","r",stdin);
    //freopen("aisle.out","w",stdout);
    cin>>n>>l>>w>>x>>y>>a;
    int numl=l/x;
    int numr;
    if (numl==0) {puts("-1");return 0;}
    if (n%numl==0) numr=n/numl;
    else numr=n/numl+1;
    int z=numr*y;
    if (w-z>=a)
    {
        int num=(w-z)/a;
        if (num<=numr/2) printf("%d\n",min(n,numl*2*num));
        else printf("%d\n",n);
    }
    else puts("-1");
    return 0;
}

在这里插入图片描述
首先要明白,B进制下D位数的范围是哪到哪,不清楚的可以拿十进制推一推
最小值就是BD-1 ,最大值是BD -1
所以要想知道有多少个数满足两个条件,相当于取区间交的长度 计算L R做比较即可
注意超过1e18都不算,为了防止计算溢出,通常使用除法来判断。左端点溢出一定无解,右端点溢出就让它成为1e18

当然,本题可以难度继续升级,移除1e18限制 。

#include<bits/stdc++.h>
using namespace std;
int j,b1,d1,b2,d2;
unsigned long long l1=1,r1,l2=1,r2;
bool f;
int main() {
	cin>>b1>>d1>>b2>>d2;
	r1=r2=1;
	for(int i=2; i<=d1; i++) {
		if(l1<=1e18/b1)l1*=b1;
		else {
			cout<<0;
			return 0;
		}
	}
	for(int i=1; i<=d1; i++)
		if(r1<=1e18/b1)r1*=b1;
		else {
			r1=1e18;
			r1++;
			break;
		}
	r1--;
	for(int i=2; i<=d2; i++) {
		if(l2<=1e18/b2)l2*=b2;
		else {
			cout<<0;
			return 0;
		}
	}
	for(int i=1; i<=d2; i++)
		if(r2<=1e18/b2)r2*=b2;
		else {
			r2=1e18;
			r2++;
			break;
		}
	r2--;
	if(l1>l2) {
		swap(l1,l2);
		swap(r1,r2);
	}
	if(l1<=l2&&r2<=r1)cout<<r2-l2+1;
	else if(l2<=l1&&r1<=r2)cout<<r1-l1+1;
	else if(r1>=l2)cout<<r1-l2+1;
	else cout<<0;
	return 0;
}

在这里插入图片描述
相信学了DFS的同学多少还是能做一点分数的,暴力DFS 相当于求12的排列然后代入对应编号的边去检查,小范围内还是可以拿分的。

实际上本题T组数据,每次都这样子去DFS肯定行不通的,我们考虑对12的排列的寻找进行优化

考虑编号摆放
1 2 3 4 5 6 7 8 9 10 11 12
3 2 1 6 5 4 9 8 7 12 11 10
10 11 12 7 8 9 6 5 4 1 2 3

不难发现这几种不同的排列,实际上产生的四个三角形是一模一样的所以说我们要尽量剔除这种本质相同的排列情况。如何DFS剪枝?
回忆一下最初的一个问题,N个数选R个数并且要求都按字典序输出,保证不重复不遗漏,所以说我们DFS寻找本质不同的12排列时,外部4个块要保证有序,且每个块内部也要保证有序,这样子就能不重复不遗漏。
预处理DFS寻找本质不同的12排列存起来,每次查询的时候直接枚举排列带进去检查

#include<cstdio>  
#include<iostream>  
#include<algorithm>  
#include<cstdlib>  
#include<cstring>
#include<string>
#include<climits>
#include<vector>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<ctime>
#define LL long long
#define inf 0x3f3f3f3f
#define P pair<int,int>

using namespace std;

inline char nc(){
    /* 
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
    */return getchar();
}
  
inline void read(int &x){
  char c=nc();int b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
  
inline void read(LL &x){
  char c=nc();LL b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
 
inline void read(char &x){
  for (x=nc();!(x=='('||x==')');x=nc());
}

inline int read(char *s)
{
    char c=nc();int len=1;
    for(;!(c=='0'||c=='1');c=nc()) if (c==EOF) return 0;
    for(;(c=='0'||c=='1');s[len++]=c,c=nc());
    s[len++]='\0';
    return len-2;
}
int wt,ss[19];
inline void print(int x){
    if (x<0) x=-x,putchar('-'); 
    if (!x) putchar(48); else {
    for (wt=0;x;ss[++wt]=x%10,x/=10);
    for (;wt;putchar(ss[wt]+48),wt--);}
}
inline void print(LL x){
    if (x<0) x=-x,putchar('-');
    if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
}

int T,n,m,s,a[20],b[5],res,ans[5][5];
int f[15500*24][20];

bool check(int x,int y,int z)
{
    if (x+y<=z) return false;
    if (x+z<=y) return false;
    if (y+z<=x) return false;
    return true;
}

void dfs(int x)
{
    if (x==13)
    {
        if (a[1]<a[4]&&a[4]<a[7]&&a[7]<a[10])
        {
            s++;
            for (int i=1;i<=12;i++)
                f[s][i]=a[i];
        }
        return ;
    }
    for (int i=1;i<=12;i++)
        if (b[i]==0)
        {
            b[i]=1;
            a[x]=i;
            if (x%3==0)
            {
                if (a[x]>a[x-1]&&a[x-1]>a[x-2]) dfs(x+1);
            }
            else dfs(x+1);
            b[i]=0;
        }
}

void solve(int b[])
{
    int s=0;
    for(int i=1;i<=4;i++)
        if (check(a[b[i*3-2]],a[b[i*3-1]],a[b[i*3]])) s++;
    if (s>res)
    {
        res=s;    
    }
}

int main()
{
    read(T);
    int p=0;
    s=0;
	dfs(1);
    while(T--)
    {
        for (int i=1;i<=12;i++)
        read(a[i]);
        res=0;
		for (int i=1;i<=s;i++)
        solve(f[i]);
        printf("%d\n",res);
    }
    return 0;
}

在这里插入图片描述
一定要把题目读懂,题目的要求是什么,操作又是什么。
题目最终想让
A [ 1 ] = A [ m + 1 ] A[1]=A[m+1] A[1]=A[m+1]
A [ 2 ] = A [ m + 2 ] A[2]=A[m+2] A[2]=A[m+2]
A [ m + 1 ] = A [ 2 m + 1 ] A[m+1]=A[2m+1] A[m+1]=A[2m+1]
A [ n − m ] = A [ n ] A[n-m]=A[n] A[nm]=A[n]
相当于每M个数为一个块,每个块内的第 i i i个位置最终都要变得一样
观察数据范围
在这里插入图片描述
凭直接来讲应该也是有40分是白送的(实际上也确实是暴力得分)
两个操作,一个是单点修改,一个是前缀区间翻转
不难发现完全没必要把同一段前缀区间做翻转两次,因为相当于没翻转
所以我们可以选择翻转M,2M,3M…KM 注意KM ≥ ≥ N
所以当M ≥ ≥ n \sqrt{n} n 时,K ≤ ≤ n \sqrt{n} n 所以小范围的时候,可以暴力DFS检查翻转哪些前缀区间,然后剩下不一样的点直接暴力修改,统计答案 40分到手。

但是当M ≤ ≤ n \sqrt{n} n 时就不能暴力搜索了 ,我们考虑状态转移
定义 d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] dp[i][j][0/1]表示处理完第 i  最后一块 i~最后一块 i 最后一块并且都使得块内状态为 j 状态压缩 j状态压缩 j状态压缩 并且有没有使用区间翻转的最优操作次数
首先计算我们有多少块,直接按 N / M N/M N/M划分一下得到 K K K

在不使用操作2的情况下,既然 j j j是状态压缩后的结果,所以我们要讨论本状态下,块内的每个位置到底最终是放的是什么,如果跟本位置的符号不一样,那么就要使用操作1修改为 j j j状态对应的结果
最终得到 d p [ K ] [ j ] [ 0 ] dp[K][j][0] dp[K][j][0]的值

同理,在使用操作2的情况下,既然 j j j是状态压缩后的结果,所以我们要讨论本状态下,块内的每个位置到底最终是放的是什么,如果跟本位置的符号一样,因为现在使用了翻转 K ∗ M K*M KM这一段,所以如果未操作前 j j j状态对于某些位上的值已经匹配了,翻转后会变得不匹配,需要单点修改,那么就要使用操作1修改为 j j j状态对应的结果 。
举个例子, j j j状态为10101010 字符串当前这一段内容为10101010,虽然现在跟 j j j状态都是匹配的,但是翻转后,会全部失效,所以说本情况,匹配的时候反而需要修改。
最终得到 d p [ K ] [ j ] [ 1 ] dp[K][j][1] dp[K][j][1]的值

考虑如何向前面的块转移?倒着继续枚举K-1 K-2 …1
按照上面的方法计算,枚举最终状态,然后枚举对应的块内位置以及字符串内的实际位置
先统计单点修改的次数,考虑合并状态转移

 dp[k][j][0]=min(dp[k+1][j][0]+ans,dp[k+1][j][1]+1+ans);  
 [第K块] [k+1~最后一块] 进行合并     如果[k+1~最后一块]没使用前缀区间翻转就变成了j状态
 那么合并成[k~最后一块]没前缀区间翻转就变成了j状态的最小次数就有了一种方案 dp[k+1][j][0]+ans

但是还有另一种情况,[k+1~最后一块]使用了前缀区间翻转才变成了j状态,
因为会连带翻转第K块,所以要翻转另一段前缀区间抵消这个翻转,所以操作+1
然后再合并本块的操作次数ans  

后面的转移就同理了
最后最终状态一定在 d p [ 1 ] [ j ] [ 0 / 1 ] dp[1][j][0/1] dp[1][j][0/1]上,表示把[1~最后一块]翻转成为 j j j状态的最优次数 取min即可

#include<cstdio>  
#include<iostream>  
#include<algorithm>  
#include<cstdlib>  
#include<cstring>
#include<string>
#include<climits>
#include<vector>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<ctime>
#define LL long long
#define inf 0x3f3f3f3f
#define P pair<int,int>

using namespace std;

inline char nc(){
    /* 
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
    */return getchar();
}
  
inline void read(int &x){
  char c=nc();int b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
  
inline void read(LL &x){
  char c=nc();LL b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
 
inline void read(char &x){
  for (x=nc();!(x=='('||x==')');x=nc());
}

inline int read(char *s)
{
    char c=nc();int len=1;
    for(;!(c=='0'||c=='1');c=nc()) if (c==EOF) return 0;
    for(;(c=='0'||c=='1');s[len++]=c,c=nc());
    s[len++]='\0';
    return len-2;
}
int wt,ss[19];
inline void print(int x){
    if (x<0) x=-x,putchar('-'); 
    if (!x) putchar(48); else {
    for (wt=0;x;ss[++wt]=x%10,x/=10);
    for (;wt;putchar(ss[wt]+48),wt--);}
}
inline void print(LL x){
    if (x<0) x=-x,putchar('-');
    if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
}

int n,m,k,f[310][27000][2],ans,a[20];
char s[310];

void dfs(int x)
{
    if (x==k+1)
    {
        int zero[310],one[310],flag=0;
        for (int i=1;i<=m;i++)
            zero[i]=0,one[i]=0;
        for (int i=k;i>=1;i--)
        {	//检查要不要翻转一下 i*m   
            if (a[i]) flag=1-flag;
            if (flag==1)
            {					//某一段m翻转内  统计本段本位翻转后有多少0  1 
                for (int p=1,j=(i-1)*m+1;j<=min(n,i*m);j++,p++)
                    if (s[j]=='0') one[p]++;else zero[p]++;
            }
            else	//不翻 
            {
                for (int p=1,j=(i-1)*m+1;j<=min(n,i*m);j++,p++)
                    if (s[j]=='1') one[p]++;else zero[p]++;
            }
        }
        int res=0;
        for (int i=1;i<=m;i++)
        {
             res+=min(zero[i],one[i]);
           // else 	    res+=min(zero[i],one[i]);
        }
        for (int i=1;i<=k;i++)
            if (a[i]) res++;
        ans=min(ans,res);
        return ;
    }
    for (int i=0;i<=1;i++)
        a[x]=i,dfs(x+1);//处理第x段的反转   翻转还是不翻转  
}

int main()
{
    n=read(s);
    read(m);
    if (m<=sqrt(n))
    {
        int k=n/m;
        if (n%m!=0) k++;
        for (int i=0;i<(1<<m);i++)
        {
            int ans=0;
            for (int p=0,j=(k-1)*m+1;j<=min(n,k*m);j++,p++)
            {
                if ((i&(1<<p))){if (s[j]=='0') ans++;}
                else {if (s[j]=='1') ans++;}
            }
            f[k][i][0]=ans;ans=1;
            for (int p=0,j=(k-1)*m+1;j<=min(n,k*m);j++,p++)
                if ((i&(1<<p))){if (s[j]=='1') ans++;}
                else {if (s[j]=='0') ans++;}
            f[k][i][1]=ans;
        }
        for (k--;k>=1;k--)
            for (int i=0;i<(1<<m);i++)
            {
                int ans=0;
                for (int p=0,j=(k-1)*m+1;j<=min(n,k*m);j++,p++)
                    if ((i&(1<<p))){if (s[j]=='0') ans++;}
                    else {if (s[j]=='1') ans++;}
                f[k][i][0]=min(f[k+1][i][0]+ans,f[k+1][i][1]+1+ans);
                ans=0;
                for (int p=0,j=(k-1)*m+1;j<=min(n,k*m);j++,p++)
                    if ((i&(1<<p))){if (s[j]=='1') ans++;}
                    else {if (s[j]=='0') ans++;}
                f[k][i][1]=min(f[k+1][i][1]+ans,f[k+1][i][0]+1+ans);
            }
        int ans=min(f[1][0][0],f[1][0][1]);
        for (int i=0;i<(1<<m);i++)
            ans=min(ans,f[1][i][0]),
            ans=min(ans,f[1][i][1]);
        print(ans),puts("");
    }
    else
    {	//暴力反转  枚举   
        k=n/m;//  n/m检查最多把前k*m段翻转 
        if (n%m!=0) k++;
        ans=n;
        dfs(1);
        print(ans),puts("");
    }
    return 0;
}

路漫漫其修远兮,学习还需踏实勤奋

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值