【HHHOJ】NOIP模拟赛 玖 解题报告

点此进入比赛

得分: \(100+20+100=220\)(还不错)

排名: \(Rank\ 16\)

\(Rating\)\(+20\)

\(T1\):【HHHOJ263】「NOIP模拟赛 玖」三个和尚(点此看题面

第一眼看完毫无想法。

仔细思考,可以发现一个性质:只要原数数位之和\(sum\)不能被\(3\)整除,就说明无解

然后就很简单了,只要使输出的\(3\)个数数位之和全部为\(\frac{sum}3\)即可。

代码如下:

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1)) 
#define N 10000 
using namespace std;
int n=0,num[N+5];
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
        #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
    public:
        FIO() {FinNow=FinEnd=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline bool read_digit(int &x) {while(!isdigit(x=tc())) if(!~x) return false;return x&=15,true;}
        inline void read_char(char &x) {while(isspace(x=tc()));}
        inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
        inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
        inline void write_char(char x) {pc(x);}
        inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
        inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
inline void Solve(int x,int v)//x表示当前处理到的位数,v表示剩余的数位和
{
    if(num[x]>=v) return (void)(F.write_char(v+48),num[x]-=v);//如果该位数字≥v,就输出v,并将该位数字减去v
    Solve(x-1,v-num[x]),F.write_char(num[x]+48),num[x]=0;//如果该位数字<v,就处理更高的一位,然后输出该位数字(注意,这顺序不能反),然后将该位数字赋值为0
}
int main()
{
    register int i,x,sum=0;
    while(F.read_digit(x)) sum+=(num[++n]=x);//用sum统计数位之和
    if(sum%3) return puts("-1"),0;//如果sum不能被3整除,说明无解
    for(i=1;i<=3;++i) Solve(n,sum/3),F.write_char(' ');//输出答案
    return F.end(),0;
}

\(T2\):【HHHOJ264】「NOIP模拟赛 玖」疯狂祖玛(点此看题面

比赛时写了一个区间\(DP\),样例水过,交上去挂了,只有\(20\)分... ...

正解貌似是一个更加高级的区间\(DP\),但是要用记忆化搜索实现,思路十分巧妙。

为了简便,我们可以用\(NeedBall(x)\)表示\(x\)个颜色相同且连续的珠子时还需加多少个珠子才能将其消掉。具体实现如下:

#define NeedBall(x) ((x)<k?k-(x):0)

首先,我们把颜色相同且连续的珠子压缩在一起,用\(col_i\)表示颜色,\(num_i\)表示个数。

然后,考虑用\(f_{l,r,v}\)表示在编号为\(r\)的珠子右边有\(v\)个与\(r\)号珠子同色的珠子时,要把\([l,r]\)区间内的珠子消完所需要增加的最少珠子数

则有两种转移方式:

  • 直接加上\(Need(v+num_r)\)个珠子把\(r\)号珠子消掉。即\(f_{l,r,v}=f_{l,r-1,0}+Need(v+num_r)\)
  • 找到\([l,r]\)范围内的一个\(i\)使其满足\(col_i=col_r\),我们可以选择将\([i+1,r-1]\)之间的珠子消掉,然后将\(i\)\(r\)两块珠子连在一起。即\(f_{l,r,v}=min(f_{l,r,v},f_{l,i,min(k,v+num_r)}+f_{i+1,r-1,0})\)

代码如下:

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1)) 
#define N 100
#define K 5 
#define AddBalls(y,z) (s[++n]=Balls(y,z),lst[n]=End[y],End[y]=n)
#define NeedBall(x) ((x)<k?k-(x):0)//求出还需多少个球
using namespace std;
int n,k,lst[N+5],End[N+5];
struct Balls//记录连续一段颜色相同的求的信息
{
    int col,num;//col记录颜色,num记录数量
    Balls(int x=0,int y=0):col(x),num(y){}
}s[N+5];
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
        #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
    public:
        FIO() {FinNow=FinEnd=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void read_char(char &x) {while(isspace(x=tc()));}
        inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
        inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
        inline void write_char(char x) {pc(x);}
        inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
        inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
class Class_DP
{
    private:
        int f[N+5][N+5][K+5];//f[l][r][v]表示在编号为r的珠子右边有v个与r号珠子同色的珠子时,要把[l,r]区间内的珠子消完所需要增加的最少珠子数
    public:
        inline void Init() {for(register int i=0,j,l;i<=n;++i) for(j=i;j<=n;++j) for(l=0;l<=k;++l) f[i][j][l]=-1;}//初始化全部为-1,方便记忆化
        inline int GetAns(int l,int r,int v)//记忆化搜索
        {
            if(~f[l][r][v]) return f[l][r][v];//如果已经求解过,返回求出的答案
            register int i,t;
            for(f[l][r][v]=GetAns(l,r-1,0)+NeedBall(s[r].num+v),i=l;i<r;++i)
                if(!(s[i].col^s[r].col)) t=GetAns(l,i,min(k,s[r].num+v))+GetAns(i+1,r-1,0),f[l][r][v]=min(f[l][r][v],t);//状态转移
            return f[l][r][v];  
        }
}DP;
int main()
{
    register int i,nn,x,y,z=1;
    for(F.read(nn),F.read(k),F.read(y),--nn;nn;--nn) F.read(x),(x^y?(AddBalls(y,z),y=x,z=1):++z);
    return AddBalls(y,z),DP.Init(),F.write(DP.GetAns(1,n,0)),F.end(),0;//输出答案
}

\(T3\):【HHHOJ265】「NOIP模拟赛 玖」回文单词(点此看题面

说实话,这题就是一个比较简单的哈希

考虑题意,其实不难有一个贪心的想法:每次选取最短的相同的前缀和后缀,将其从字符串中删去,并将答案加\(1\)

关于如何求出最短的相同的前缀和后缀,我首先想到的是利用\(KMP\)算法\(Next\)数组。

后来发现不行(因为前缀是在变化的)。

所以我们要用更灵活的字符串匹配方式:字符串哈希

个人感觉得出上面那个结论,这道题就不难了。

具体实现可以看下面的代码:

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1)) 
#define N 1000000
using namespace std;
string st; 
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
        #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
    public:
        FIO() {FinNow=FinEnd=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void read_char(char &x) {while(isspace(x=tc()));}
        inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
        inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
        inline void write_char(char x) {pc(x);}
        inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
        inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
class Class_HashSolver//用字符串哈希求解答案
{
    private:
        struct Hash//双哈希
        {
            ull fir,sec;
            Hash(ull x=0,ull y=0):fir(x),sec(y){}
        }seed,s[N+5],t[N+5];
        inline friend Hash operator + (Hash x,Hash y) {return Hash(x.fir+y.fir,x.sec+y.sec);}//重载加法
        inline friend Hash operator - (Hash x,Hash y) {return Hash(x.fir-y.fir,x.sec-y.sec);}//重载减法
        inline friend Hash operator * (Hash x,Hash y) {return Hash(x.fir*y.fir,x.sec*y.sec);}//重载乘法
        inline friend bool operator == (Hash x,Hash y) {return !(x.fir^y.fir||x.sec^y.sec);}//重载等于号
    public:
        Class_HashSolver() {seed=Hash(31,41),t[0]=Hash(1,1);for(register int i=1;i<=N;++i) t[i]=t[i-1]*seed;}//初始化
        inline void Init(string st) {s[0]=Hash(1,1);for(register int i=1,len=st.length();i<=len;++i) s[i]=s[i-1]*seed+Hash(st[i-1]&31,st[i-1]&31);}//对字符串进行哈希 
        inline bool check(int l1,int r1,int l2,int r2) {return (s[r1]-(s[l1-1]*t[r1-l1+1]))==(s[r2]-(s[l2-1]*t[r2-l2+1]));}//比较两段区间内的字符串是否相同
        inline int GetAns(int l,int r)//求解[l,r]区间内的答案
        {
            if(l>r) return 0; 
            for(register int i=l;(i<<1)<(l+r);++i) if(check(l,i,l+r-i,r)) return GetAns(i+1,l+r-i-1)+2;//找到最短的相同的前缀和后缀,将其从字符串中删去,并将答案加2 
            return 1;//否则,这整个字符串对答案造成的贡献为1
        }
}HashSolver;
int main()
{
    register int T;F.read(T);
    while(T--) F.read_string(st),HashSolver.Init(st),F.write(HashSolver.GetAns(1,st.length())),F.write_char('\n');
    return F.end(),0;
}

转载于:https://www.cnblogs.com/chenxiaoran666/p/HHHOJ_NOIP2018_nine.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值