In mathematics you don't understand things, you just get used to them.

标签 dp 下的文章

Description

Link.

Summarizing the fucking statement is the last thing in the world I ever want to do.

Solution

我们来重新描述一些概念:

  • 数字牌:筒条万。
  • 顺子:$3$ 张连续的数字牌。(面子)
  • 雀头:$2$ 张完全一样的牌。
  • 刻子:$3$ 张完全一样的牌。(面子)
  • 杠子:$4$ 张完全一样的牌。

观察发现:杠子在除了 $14$ 张牌胡牌情况以外的胡牌情况都出现了,于是又发现:$\binom{4}{3}>\binom{4}{4}$;

于是:

  • 对于胡牌的形式,我们只需要考虑「$3\times4+2$」「七对子」和「国士无双」。

于是我们只需要三种情况取最大即可。

  1. 「七对子」只需要把所有牌型的贡献拉出来,取前 $7$ 个最大的乘起来即可。
  2. 「国士无双」枚举谁来做 $2$ 个的那个,然后取最大。
  3. 「$3\times4+2$」

$\qquad$ 考虑这样一个 DP,设:$f(i,j,k,a,b,c)$ 为前 $i$ 个数,$j$ 个面子,$k$ 个雀头,第 $i$ / $i+1$ / $i+2$ 张牌用了 $a$ / $b$ / $c$ 张的最优答案($k\in\{0,1\}$)。

$\qquad$ $\qquad$ 1. 对于雀头:

$$ f(i,j,1,a+2,b,c)=\max\{\frac{f(i,j,0,a,b,c)\times\text{calc}(i,a+2)}{\text{calc}(i,a)}\} $$

$\qquad$ $\qquad$ $\qquad$ 其中 $\text{calc}(x,y)$ 为第 $x$ 张牌,在手牌中需要出现 $y$ 次,此时对答案的贡献。

$\qquad$ $\qquad$ $\qquad$ 方程的意义即:去掉把第 $i$ 种牌之前的贡献,再算上现在算作雀头的贡献。

$\qquad$ $\qquad$ 2. 对于刻子:

$$ f(i,j+1,0\text{ or }1,a+3,b,c)=\max\{\frac{f(i,j,0\text{ or }1,a,b,c)\times\text{calc}(i,a+3)}{\text{calc}(i,a)}\} $$

$\qquad$ $\qquad$ $\qquad$ 基本同上。

$\qquad$ $\qquad$ 3. 对于顺子:

$$ f(i,j+1,0\text{ or }1,a+1,b+1,c+1)=\max\{\frac{f(i,j,0\text{ or }1,a,b,c)\times\text{calc}(i,a+1)\times\text{calc}(i+1,b+1)\times\text{calc}(i+2,c+1)}{\text{calc}(i,a)\times\text{calc}(i+1,b)\times\text{calc}(i+2,c)}\} $$

$\qquad$ $\qquad$ $\qquad$ 完全同理。

然后就完了。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL koshi[15]={0,1,9,10,18,19,27,28,29,30,31,32,33,34}; // the cards that Kokushimusou needs
const bool chunko[36]={0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1}; // the cards which are able to be jyunko
LL getCard()
{
    static char s[10];
    scanf("%s",s+1);
    if(s[1]=='0')    return -1;
    else if(!isdigit(s[1]))
    {
        if(s[1]=='E')    return 28;
        else if(s[1]=='S')    return 29;
        else if(s[1]=='W')    return 30;
        else if(s[1]=='N')    return 31;
        else if(s[1]=='Z')    return 32;
        else if(s[1]=='B')    return 33;
        else    return 34;
    }
    else
    {
        if(s[2]=='m')    return s[1]-'0';
        else if(s[2]=='p')    return s[1]-'0'+9;
        else    return s[1]-'0'+18;
    }
}
LL t,comb[10][10],cnt[40],trs[40],f[40][5][5][5][5][5];
#define calc(x,f) (((cnt[x])<(f)?0:comb[cnt[x]][f])*(trs[x]?(1<<(f)):1))
LL onesolve() // Seven Pairs
{
    vector<LL> pri;
    for(LL i=1;i<=34;++i)    if(cnt[i]>=2)    pri.push_back(calc(i,2));
    sort(pri.begin(),pri.end(),greater<LL>());
    if(pri.size()<size_t(7))    return 0;
    else
    {
        LL res=7;
        for(LL i=0;i<7;++i)    res*=pri[i];
        return res;
    }
}
LL anosolve() // Kokushimusou
{
    LL flag=0;
    for(LL i=1;i<=13;++i)
    {
        if(cnt[koshi[i]]>=2)    flag=1;
        if(cnt[koshi[i]]==0)    return 0;
    }
    if(flag)
    {
        LL res=0;
        for(LL i=1;i<=13;++i)
        {
            LL tmp=13;
            if(cnt[koshi[i]]>=2)
            {
                for(LL j=1;j<=13;++j)    tmp*=calc(koshi[j],(i==j)+1);
            }
            res=max(res,tmp);
        }
        return res;
    }
    else    return 0;
}
void getmax(LL &one,LL ano){one=max(one,ano);}
LL exsolve() // 3x4+2
{
    #define f(i,j,k,a,b,c) (f[i][j][k][a][b][c])
    f(1,0,0,0,0,0)=1;
    for(LL i=1;i<=34;++i)
    {
        for(LL j=0;j<=4;++j)
        {
            for(LL a=0;a<=4;++a)
            {
                for(LL b=0;b<=2;++b)
                {
                    for(LL c=0;c<=2;++c)
                    {
                        if(cnt[i]-a>=2)    getmax(f(i,j,1,a+2,b,c),f(i,j,0,a,b,c)/calc(i,a)*calc(i,a+2));
                        if(j^4)
                        {
                            if(cnt[i]-a>=3)
                            {
                                getmax(f(i,j+1,0,a+3,b,c),f(i,j,0,a,b,c)/calc(i,a)*calc(i,a+3));
                                getmax(f(i,j+1,1,a+3,b,c),f(i,j,1,a,b,c)/calc(i,a)*calc(i,a+3));
                            }
                            if(chunko[i]&&cnt[i]-a>=1&&cnt[i+1]-b>=1&&cnt[i+2]-c>=1&&(b^2)&&(c^2))
                            {
                                getmax(f(i,j+1,0,a+1,b+1,c+1),f(i,j,0,a,b,c)/calc(i,a)/calc(i+1,b)/calc(i+2,c)*calc(i,a+1)*calc(i+1,b+1)*calc(i+2,c+1));
                                getmax(f(i,j+1,1,a+1,b+1,c+1),f(i,j,1,a,b,c)/calc(i,a)/calc(i+1,b)/calc(i+2,c)*calc(i,a+1)*calc(i+1,b+1)*calc(i+2,c+1));
                            }
                        }
                        getmax(f(i+1,j,0,b,c,0),f(i,j,0,a,b,c));
                        getmax(f(i+1,j,1,b,c,0),f(i,j,1,a,b,c));
                    }
                }
            }
        }
    }
    LL res=0;
    for(LL i=1;i<=34;++i)
    {
        for(LL a=0;a<=4;++a)
        {
            for(LL b=0;b<=2;++b)
            {
                for(LL c=0;c<=2;++c)    getmax(res,f(i,4,1,a,b,c));
            }
        }
    }
    #undef f
    return res;
}
int main()
{
    for(LL i=0;i<=4;++i)    comb[i][0]=comb[i][i]=1;
    for(LL i=1;i<=4;++i)    for(LL j=1;j<i;++j)    comb[i][j]=comb[i-1][j-1]+comb[i-1][j];
    scanf("%lld",&t);
    while(t--)
    {
        memset(f,0,sizeof(f));
        for(LL i=1;i<=34;++i)    cnt[i]=4,trs[i]=0;
        LL tmp=getCard();
        while(~tmp)    --cnt[tmp],tmp=getCard();
        tmp=getCard();
        while(~tmp)    trs[tmp]=1,tmp=getCard();
        printf("%lld\n",max(max(onesolve(),anosolve()),exsolve()));
    }
    return 0;
}

Description

Link.

给出一个数列,要求将其分成几段,使每段的和非严格递增,求最小的每段的和的平方和。

Solution

注意到 $a_{i}$ 为正,对于正数有这样的结论:

$$ a^{2}+b^{2}<(a+b)^{2} $$

正确性显然。这告诉我们,分的段越多越好。

现在我们来考虑如何使每段的和非严格递增。

考虑暴力 DP,设 $f_{i,j}$ 为前 $i$ 个数然后上一个 breakpoint 是 $j$​ 的最小平方和,转移:

$$ f_{i,j}=\min_{1\le k<j,pre_{i}-pre_{j}\ge pre_{j}-pre_{k}}\{f_{j,k}+(pre_{i}-pre_{j})^{2}\} $$

$pre$ 为前缀和。当然这个 DP 什么结论都没用,没有任何技术含量。

所以我们考虑优化。

你会发现这个 $k$ 不是每次都要重头枚举,他是单调的,于是你维护 $f_{j,k}$ 最小值即可,复杂度变成了 $\mathcal{O}(n^{2})$。

考虑上面的结论,你会发现我们只需要找打第一个可行的转移点 $k$ 即可。

意味着我们可以维护一个 $g_{i}$ 来表示:前 $i$ 个数,我们最大的一个合法 $k$。这个东西肯定是单调不降的。

转移即:

$$ g_{i}=\max_{pre_{i}-pre{j}\ge pre_{j}-pre_{g_{j}}}\{j\} $$

变形得:

$$ g_{i}=\max_{pre_{i}\ge pre_{j}\times2-pre_{g_{j}}}\{j\} $$

显然判定条件的右边有单调性($pre_{j}+(pre_{j}-pre_{g_{j}})$),左边也有单调性。

然后单调队列优化即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef __int128 _LL;
int n,a[40000010],taskID,f[40000010],que[40000010],head,tail;
LL pre[40000010];
_LL ans;
template<typename T>
void read(T &hhh)
{
    T x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')    f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')    x=(x<<3)+(x<<1)+(c^'0'),c=getchar();
    if(~f)    hhh=x;
    else    hhh=-x;
}
template<typename T>
void write(T x,char las='\n')
{
    static int stk[100],top=0,f=0;
    if(x<0)    x=-x,f=1;
    do stk[++top]=x%10,x/=10; while(x);
    if(f)    putchar('-');
    while(top)    putchar(stk[top--]^'0');
    putchar(las);
}
void generateData(int taskID)
{
    if(taskID)    for(int i=1;i<=n;++i)    read(a[i]);
    else
    {
        static int p[100010],l[100010],r[100010],b[40000010];
        LL x,y,z;
        int m;
        read(x),read(y),read(z),read(b[1]),read(b[2]),read(m);
        const int MOD=(1<<30);
        for(int i=1;i<=m;++i)    read(p[i]),read(l[i]),read(r[i]);
        for(int i=3;i<=n;++i)    b[i]=(LL(x)*b[i-1]%MOD+LL(y)*b[i-2]%MOD+z)%MOD;
        for(int j=1;j<=m;++j)
        {
            for(int i=p[j-1]+1;i<=p[j];++i)    a[i]=(b[i]%(r[j]-l[j]+1))+l[j];
        }
    }
    for(int i=1;i<=n;++i)    pre[i]=pre[i-1]+a[i];
}
int main()
{
    read(n),read(taskID);
    generateData(taskID^1);
    for(int i=1;i<=n;++i)
    {
        while(head<tail&&pre[i]>=(pre[que[head+1]]<<1)-pre[f[que[head+1]]])    ++head;
        f[i]=que[head];
        while(head<tail&&(pre[i]<<1)-pre[f[i]]<=(pre[que[tail]]<<1)-pre[f[que[tail]]])    --tail;
        que[++tail]=i;
    }
    for(int i=n;i;i=f[i])    ans+=_LL(pre[i]-pre[f[i]])*(pre[i]-pre[f[i]]);
    write(ans);
    return 0;
}

Description

Link.

给定字符串,正整数集合 $A,B$,满足 $\forall u\in A,v\in B,1\le u,v\le n$。

求 $\sum_{i\in A}\sum_{j\in B}\text{LCP}(A,B)$。

Solution

双倍经验是 SvT,只不过 SvT 这屑玩意儿卡常。

先反转串,然后插入 SAM。众所周知

把字符串反转后插入 SAM 后,两个原串的后缀在 parent tree 上的 $\text{LCA}$ 是这两个后缀的 $\text{LCP}$。

然后你就可以搞两个 DP,分别跑 $A$ 子树大小,$B$ 子树大小。

注意根节点需要特殊处理,因为我们是跨子树跑的 DP。不过 SvT 不需要,不知道是不是我的问题(应该就是)。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n,m,dfn[500010],fa[500010][21],dep[500010],sjc,pos[200010],onepower[500010],anopower[500010],onef[500010],anof[500010];
char s[200010];
LL ans;
struct SuffixAutomaton
{
    #define ID(c) ((c)-'a')
    vector<int> e[500010];
    int n,cntot,las,len[500010],pre[500010],ch[500010][26];
    char s[200010];
    void init(int _n,char c[])
    {
        n=_n;
        for(int i=1;i<=n;++i)    s[i]=c[i];
        cntot=las=1;
    }
    void extend(char c)
    {
        int cur=++cntot,one=las,ano=0;
        len[cur]=len[las]+1,las=cur;
        while(one&&!ch[one][ID(c)])    ch[one][ID(c)]=cur,one=pre[one];
        if(one==0)    pre[cur]=1;
        else
        {
            ano=ch[one][ID(c)];
            if(len[one]+1==len[ano])    pre[cur]=ano;
            else
            {
                int clone=++cntot;
                len[clone]=len[one]+1;
                pre[clone]=pre[ano];
                memcpy(ch[clone],ch[ano],sizeof(ch[ano]));
                while(one&&ch[one][ID(c)]==ano)    ch[one][ID(c)]=clone,one=pre[one];
                pre[ano]=pre[cur]=clone;
            }
        }
    }
    void build()
    {
        for(int i=1;i<=n;++i)    extend(s[i]),pos[i]=las;
        for(int i=2;i<=cntot;++i)    e[pre[i]].emplace_back(i);
    }
}SAM;
void dfs(int x,int las)
{
    dfn[x]=++sjc,fa[x][0]=las,dep[x]=dep[las]+1;
    for(int i=1;i^21;++i)    fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int y : SAM.e[x])    dfs(y,x);
}
int LCA(int one,int ano)
{
    if(dep[one]<dep[ano])    swap(one,ano);
    for(int i=20;~i;--i)    if(dep[fa[one][i]]>=dep[ano])    one=fa[one][i];
    if(one^ano)
    {
        for(int i=20;~i;--i)    if(fa[one][i]^fa[ano][i])    one=fa[one][i],ano=fa[ano][i];
        return fa[one][0];
    }
    else    return one;
}
bool cmp(int one,int ano){return dfn[one]<dfn[ano];}
struct VirtualTree
{
    vector<int> e[500010];
    vector<int> build(vector<int> poi)
    {
        sort(poi.begin(),poi.end(),cmp);
        poi.erase(unique(poi.begin(),poi.end()),poi.end());
        int len=poi.size();
        for(int i=1;i<len;++i)    poi.push_back(LCA(poi[i-1],poi[i]));
        sort(poi.begin(),poi.end(),cmp);
        poi.erase(unique(poi.begin(),poi.end()),poi.end());
        len=poi.size();
        for(int i=1;i<len;++i)    e[LCA(poi[i-1],poi[i])].push_back(poi[i]);
        return poi;
    }
}VRT;
template<class T>
void read(T &hhh)
{
    T x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')    f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')    x=(x<<3)+(x<<1)+(c^'0'),c=getchar();
    if(~f)    hhh=x;
    else    hhh=-x;
}
template<class T>
void write(T x,char las='\n')
{
    static int st[100],top=0;
    if(x<0)    putchar('-'),x=-x;
    do st[++top]=x%10,x/=10; while(x);
    while(top)    putchar(st[top--]^'0');
    putchar(las);
}
void exdfs(int x)
{
    for(int y : VRT.e[x])    exdfs(y),onef[x]+=onef[y],anof[x]+=anof[y];
    for(int y : VRT.e[x])    ans+=(LL)SAM.len[x]*(onef[x]-onef[y])*anof[y];
    ans+=(LL)((onepower[x]&anopower[x])+onepower[x]*anof[x]+anopower[x]*onef[x])*SAM.len[x];
    onef[x]+=onepower[x],anof[x]+=anopower[x];
}
int main()
{
    read(n),read(m);
    scanf("%s",s+1);
    reverse(s+1,s+n+1);
    SAM.init(n,s),SAM.build();
    dfs(1,0);
    while(m--)
    {
        int ones,anos,x;
        read(ones),read(anos);
        vector<int> key,tmp;
        while(ones--)    read(x),key.push_back(pos[n-x+1]),onepower[pos[n-x+1]]=1;
        while(anos--)    read(x),key.push_back(pos[n-x+1]),anopower[pos[n-x+1]]=1;
        tmp=VRT.build(key);
        ans=0,exdfs(tmp[0]);
        write(ans);
        for(int now : tmp)    onef[now]=anof[now]=0,VRT.e[now].clear(),onepower[now]=anopower[now]=0;
    }
    return 0;
}

Description

Link.

  • 游戏在 $4\times4$ 的菱形棋盘上进行;
  • 两名玩家轮流放置弹珠,可以在横向、纵向、$45$ 度斜线、$135$ 度斜线方向未放置弹珠的位置连续放置 $1$ 至 $3$ 颗弹珠,玩家在可以放置弹珠的情况下,必须至少放置 $1$ 颗弹珠。
  • 如果某位玩家无法再继续放置弹珠,则该名玩家输掉游戏,另外一名玩家获胜。

Solution

虽然是套路,但毕竟是之前没做过的套路,写篇题解记一下。

首先我们可以直接考虑状压,棋盘编号见图:

qw7lsky0.png

然后你打个表出来,表示所有能走的情况(状压),比如我要放棋子在 $1-5-9$ 上面,就是 $(100010001)_{2}$。

因为是用 C++ 输出的形式手打的 $82$ 种情况表,所以 generator 就不附了。

然后你打个 DP,设 $f_{S}$ 为当前棋盘状态为 $S$($S$ 的第 $i$ 为 $1$ 表示这个格子被占据,反之亦然)是先手必胜还是先手必输或者不知道(分别对应数字 $1/0/-1$)。

初始状态为 $\forall i\in[0,2^{n}-1),f_{i}=-1$;$f_{2^{n}-1}=0$。

然后你记搜一下,把所有状态搜出来。

然后就回答询问即可,只是不太清楚为什么要搞这么多字符读入卡 IO,明明多不多组都一样。

#include<bits/stdc++.h>
using namespace std;
int t,n=7,m[8]={1,2,3,4,3,2,1},id,f[(1<<16)+10];
char s[10];
const int upper=(1<<16);
const int ID[10][10]={{0},{4,1},{8,5,2},{12,9,6,3},{13,10,7},{14,11},{15}};
const int walking[90]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,17,3,18,272,48,34,6,288,36,4352,768,544,96,68,12,4608,576,72,12288,8704,1536,1088,192,9216,1152,24576,17408,3072,2176,18432,49152,34816,33,528,66,8448,1056,132,16896,2112,33792,136,273,7,1057,4368,16912,112,546,2114,14,292,1792,8736,224,33824,1092,4672,584,28672,3584,17472,2184,9344,57344,34944};
inline int unionset(int x,int y){return x|y;}
inline int intersection(int x,int y){return x&y;}
inline bool emptyset(int x){return x==0;}
void dfs(int board)
{
    if(~f[board])    return;
    for(int i=0;i<82;++i)
    {
        if(emptyset(intersection(board,walking[i])))
        {
            int newset=unionset(board,walking[i]);
            dfs(newset);
            if(f[newset]==0)
            {
                f[board]=1;
                return;
            }
        }
    }
    f[board]=0;
}
inline char fgc()
{
    static char buf[1<<17],*p=buf,*q=buf;
    return p==q&&(q=buf+fread(p=buf,1,1<<17,stdin),p==q)?EOF:*p++;
}
inline char fgop()
{
    char res=0;
    while((res^'*')&&(res^'.'))    res=fgc();
    return res;
}
inline void read(int &x)
{
    x=0;
    char c=fgc();
    while(isdigit(c)==0)    c=fgc();
    while(isdigit(c))    x=(x<<3)+(x<<1)+(c^'0'),c=fgc();
}
int main()
{
    read(t);
    memset(f,-1,sizeof(f));
    f[upper-1]=0;
    for(int i=0;i^upper;++i)
    {
        if(f[i]==-1)    dfs(i);
    }
    while(t--)
    {
        int board=0;
        for(int i=0;i<n;++i)
        {
            for(int j=0;j<m[i];++j)    board+=(fgop()=='*')?(1<<ID[i][j]):0;
        }
        printf(f[board]?"Possible.":"Impossible.");
        printf("\n");
    }
    return 0;
}

「ABC 113A」Star

Link.

略。

#include<cstdio>
int x;
int main()
{
    scanf("%d",&x);
    for(int i=1;;++i)
    {
        if((x+i)%100==0)
        {
            printf("%d\n",i);
            break;
        }
    }
    return 0;
}

「ABC 192B」uNrEaDaBlE sTrInG

Link.

略。

#include<cstdio>
#include<cstring>
char s[1010];
int main()
{
    scanf("%s",s+1);
    bool flag=1;
    int n=strlen(s+1);
    for(int i=1;i<=n;++i)
    {
        if(i&1)
        {
            if(s[i]<'a'||s[i]>'z')
            {
                flag=0;
                break;
            }
        }
        else
        {
            if(s[i]<'A'||s[i]>'Z')
            {
                flag=0;
                break;
            }
        }
    }
    printf("%s\n",flag?"Yes":"No");
    return 0;
}

「ABC 192C」Kaprekar Number

Link.

照题意模拟。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
long long las,now,n;
int k;
long long f(long long x)
{
    long long one=0,ano=0;
    vector<long long> num;
    while(x>0)
    {
        num.push_back(x%10);
        x/=10;
    }
    sort(num.begin(),num.end(),greater<long long>());
    for(auto val:num)    one=one*10+val;
    reverse(num.begin(),num.end());
    for(auto val:num)    ano=ano*10+val;
//    printf("%lld %lld\n",one,ano);
    return one-ano;
}
int main()
{
    scanf("%lld %d",&n,&k);
    las=n;
    if(k==0)    return printf("%lld\n",n)&0;
    while(k--)
    {
        now=f(las);
        las=now;
    }
    printf("%lld\n",now);
    return 0;
}

「ABC 192D」Base n

Link.

显然随着进制增大数字也会增大,所以可以二分。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
struct bigInt : vector<long long>{
    bigInt &check( ){
        while( ! empty( ) && ! back( ) ) pop_back( );
        if( empty( ) )    return *this;
        for( unsigned i = 1; i < size( ); ++ i ){ ( *this )[i] += ( *this )[i - 1] / 10; ( *this )[i - 1] %= 10; }
        while( back( ) >= 10 ){ push_back( back( ) / 10 ); ( *this )[size( ) - 2] %= 10; }
        return *this;
    }
    bigInt( long long tpN = 0 ){ push_back( tpN ); check( ); }
};
istream &operator >> ( istream &is, bigInt &tpN ){
    string s;
    is >> s; tpN.clear( );
    for( int i = s.size( ) - 1; i >= 0; --i ) tpN.push_back( s[i] - '0' );
    return is;
}
ostream &operator << ( ostream &os, const bigInt &tpN ){
    if( tpN.empty( ) )    os << 0;
    for( int i = tpN.size( ) - 1; i >= 0; --i )    os << tpN[i];
    return os;
}
bool operator != ( const bigInt &one, const bigInt &another ){
    if( one.size( ) != another.size( ) )    return 1;
    for( int i = one.size( ) - 1; i >= 0; --i ){
        if( one[i] != another[i] )    return 1;
    }
    return 0;
}
bool operator == ( const bigInt &one, const bigInt &another ){
    return ! ( one != another );
}
bool operator < ( const bigInt &one, const bigInt &another ){
    if( one.size( ) != another.size( ) )    return one.size( ) < another.size( );
    for( int i = one.size( ) - 1; i >= 0; --i ){
        if( one[i] != another[i] )    return one[i] < another[i];
    }
    return 0;
}
bool operator > ( const bigInt &one, const bigInt &another ){ return another < one; }
bool operator <= ( const bigInt &one, const bigInt &another ){ return ! (one > another ); }
bool operator >= ( const bigInt &one, const bigInt &another ){ return ! (one < another ); }
bigInt &operator += ( bigInt &one, const bigInt &another ){
    if( one.size( ) < another.size( ) )    one.resize(another.size( ) );
    for( unsigned i = 0; i != another.size( ); ++ i ) one[i] += another[i];
    return one.check( );
}
bigInt operator + ( bigInt one, const bigInt &another ){ return one += another; }
bigInt &operator -= ( bigInt &one, bigInt another ){
    if( one < another )    swap( one, another );
    for( unsigned i = 0; i != another.size( ); one[i] -= another[i], ++ i ){
        if( one[i] < another[i] ){
            unsigned j = i + 1;
            while( ! one[j] ) ++ j;
            while( j > i ){ -- one[j]; one[--j] += 10; }
        }
    }
    return one.check( );
}
bigInt operator - ( bigInt one, const bigInt &another ){ return one -= another; }
bigInt operator * ( const bigInt &one, const bigInt &another ){
    bigInt tpN;
    tpN.assign( one.size( ) + another.size( ) - 1, 0 );
    for( unsigned i = 0; i != one.size( ); ++ i ){
        for( unsigned j = 0; j != another.size( ); ++ j ) tpN[i + j] += one[i] * another[j];
    }
    return tpN.check( );
}
bigInt &operator *= ( bigInt &one, const bigInt &another ){ return one = one * another; }
bigInt divMod( bigInt &one, const bigInt &another ){
    bigInt ans;
    for( int t = one.size( ) - another.size( ); one >= another; -- t ){
        bigInt tpS;
        tpS.assign( t + 1, 0 );
        tpS.back( ) = 1;
        bigInt tpM = another * tpS;
        while( one >= tpM ){ one -= tpM; ans += tpS; }
    }
    return ans;
}
bigInt operator / ( bigInt one, const bigInt &another ){ return divMod(one, another ); }
bigInt &operator /= ( bigInt &one, const bigInt &another ){ return one = one / another; }
bigInt &operator %= ( bigInt &one, const bigInt &another ){ divMod( one, another ); return one; }
bigInt operator % ( bigInt one, const bigInt &another ){ return one %= another; }
char s[70];
int n,cntot;
bigInt m,num[70],mx;
bool check(bigInt bas)
{
    bigInt res=0,sab=1;
    for(int i=1;i<=cntot;++i)
    {
        res+=num[i]*sab;
        sab*=bas;
        if(res>m)    return false;
    }
    return true;
}
int main()
{
    cin>>(s+1)>>m;
    n=strlen(s+1);
    for(int i=n;i>=1;--i)
    {
        num[++cntot]=s[i]-'0';
        mx=max(mx,num[cntot]);
    }
    if(cntot==1)    cout<<(num[cntot]<=m)<<"\n";
    else
    {
//        bigInt l=0,r=1e18,ans=0;
//        while(l<=r)
//        {
//            bigInt mid=(l+r)/2;
//            if(check(mid))
//            {
//                l=mid+1;
//                ans=mid;
//            }
//            else    r=mid-1;
//        }
//        bigInt l=mx,r=m+1;
//        while(l+1<r)
//        {
//            bigInt mid=(l+r)/2;
//            if(check(mid))    l=mid;
//            else    r=mid;
//        }
        bigInt l=mx+1,r=m+1,ans=mx;
        while(l<=r)
        {
            bigInt mid=(l+r)/2;
            if(check(mid))    l=mid+1,ans=mid;
            else    r=mid-1;
        }
        cout<<ans-mx<<"\n";
    }
    return 0;
}

「ABC 192E」Train

Link.

我也不知道我怎么过的,反正就是 Dijkstra 板子套上去后把 if(dis[y]>dis[x]+z) 改成了 if(dis[y]>get(dis[x],k)+z),其中 get(dis[x],k) 就是算下一班车来的时间加上 dis[x] 本身。

然后就莫名其妙过了,可能算个贪心?

#include<queue>
#include<cstdio>
using namespace std;
const long long INF=1e18;
priority_queue<pair<long long,long long>,vector<pair<long long,long long> >,greater<pair<long long,long long> > > q;
vector<pair<long long,pair<long long,long long> > > e[100010];
long long n,m,st,ed,dis[100010],vis[100010];
long long get(long long val,long long k)
{
    if(val%k==0)    return val;
    else    return val+k-(val%k);
}
void find()
{
    for(long long i=1;i<=n;++i)    dis[i]=INF;
    dis[st]=0;
    q.push(make_pair(dis[st],st));
    while(!q.empty())
    {
        long long now=q.top().second;
        q.pop();
        if(!vis[now])
        {
            vis[now]=1;
            for(long long i=0;i<e[now].size();++i)
            {
                long long y=e[now][i].first,w=e[now][i].second.first,k=e[now][i].second.second;
                if(dis[y]>get(dis[now],k)+w)
                {
                    dis[y]=get(dis[now],k)+w;
                    q.push(make_pair(dis[y],y));
                }
            }
        }
    }
}
int main()
{
    scanf("%lld %lld %lld %lld",&n,&m,&st,&ed);
    for(long long i=1;i<=m;++i)
    {
        long long u,v,w,k;
        scanf("%lld %lld %lld %lld",&u,&v,&w,&k);
        e[u].push_back(make_pair(v,make_pair(w,k)));
        e[v].push_back(make_pair(u,make_pair(w,k)));
    }
    find();
    printf("%lld\n",dis[ed]==INF?-1:dis[ed]);
    return 0;
}

「ABC 192F」Potion

Link.

考虑枚举 $k$,设 $f_{i,j,c}$ 为前 $i$ 位选了 $j$ 个数 balabala。

我也不知道怎么 DP 的,可能是本能做出来的。

后面自己意会吧,反正也没难度了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long n,x,a[110],f[110][110][110],ans=1145141919810233454;
int main()
{
    scanf("%lld %lld",&n,&x);
    for(int i=1;i<=n;++i)    scanf("%lld",&a[i]);
    for(int s=1;s<=n;++s)
    {
        memset(f,-0x3f,sizeof(f));
        f[0][0][0]=0;
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=min(s,i);++j)
            {
                for(int k=0;k<n;++k)    f[i][j][k]=max(f[i-1][j][k],f[i-1][j-1][((k-a[i])%s+s)%s]+a[i]);
            }
        }
        if(f[n][s][x%s]>=0)    ans=min(ans,(x-f[n][s][x%s])/s);
    }
    printf("%lld\n",ans);
    return 0;
}