8.9 纪中集训 Day9

T1粉刷匠

Description

windy有 N 条木板需要被粉刷。
每条木板被分为 M 个格子。
每个格子要被刷成红色或蓝色。
windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。
每个格子最多只能被粉刷一次。
如果windy只能粉刷 T 次,他最多能正确粉刷多少格子?
一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。

Input

第一行包含三个整数,N M T。
接下来有N行,每行一个长度为M的字符串,'0'表示红色,'1'表示蓝色。

Output

输出一个整数,表示最多能正确粉刷的格子数。

Sample Input

3 6 3
111111
000000
001100

Sample Output

16

Hint

100%的数据,满足 1 <= N,M <= 50 ; 0 <= T <= 2500 。

考场思路/正解 

一眼看去感觉就是DP,于是就开始疯狂地推状态,等推出来后已经过了一个半小时了(太弱,太弱)。

细读题目,由于分为n块木板,所以我们可以将每块木板分开处理。通过博主一系列严密的推算,结合日象和方位等玄学,认为可设f[i][j],表示每一行到第i个数粉刷j次的最优解,zx[i][j]表示到第i行粉刷j次的最优解。从而便可推出方程式(具体方程式见程序)。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n,m,t,ans;
int w[55][55],f[55][2525]/*f[i][j]表示每一行到第i个数粉刷j次的最优解*/,zx[55][2525]/*zx[i][j]表示到第i行粉刷j次的最优解*/;
char ch[55][55];

int main()
{
    scanf("%d%d%d",&n,&m,&t);
    for(int i=1;i<=n;i++)
        scanf("%s",ch[i]+1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            w[i][j]=w[i][j-1]+ch[i][j]-'0';
    for(int l=1;l<=n;l++)
    {
        memset(f,0,sizeof(f));
        for(int i=1;i<=m;i++)
            for(int j=1;j<=min(m,t);j++)
                for(int k=1;k<=i;k++)
                    f[i][j]=max(f[i][j],f[k-1][j-1]+max(w[l][i]-w[l][k-1],i-(k-1)-(w[l][i]-w[l][k-1])));
        for(int i=1;i<=t;i++)
            for(int j=1;j<=min(i,m);j++)
                zx[l][i]=max(zx[l][i],zx[l-1][i-j]+f[m][j]);
    }
    for(int i=1;i<=t;i++)
        ans=max(ans,zx[n][i]);
    printf("%d",ans);
    return 0;
} 

T2迷路

Description

windy在有向图中迷路了。
该有向图有 N 个节点,windy从节点 0 出发,他必须恰好在 T 时刻到达节点 N-1。
现在给出该有向图,你能告诉windy总共有多少种不同的路径吗?
注意:windy不能在某个节点逗留,且通过某有向边的时间严格为给定的时间。

Input

第一行包含两个整数,N T。
接下来有 N 行,每行一个长度为 N 的字符串。
第i行第j列为'0'表示从节点i到节点j没有边。
为'1'到'9'表示从节点i到节点j需要耗费的时间。

Output

输出一个整数,可能的路径数,这个数可能很大,只需输出这个数除以2009的余数。

Sample Input

2 2
11
00

Sample Output

1

Hint

100%的数据,满足 2 <= N <= 10 ; 1 <= T <= 1000000000 。

考场思路

毒啊,虽然考试时候没有打,但是认为应该是用SPFA等最短路算法然后再一堆剪枝,结果考试出来看了正解发现与此没有太大关系。

正解

用矩阵乘法暴力三重for去算方案数,可是如何处理边权不等的问题呢?观察数据范围,边权的取值只为0~9,点数n<=10,所以我们可以暴力拆点,将其拆成边权为一的链,然后题目就解决了。不过由于第一次接触矩阵乘法求方案数所以改的有点久(菜啊)。

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
#define Mod 2009
using namespace std;

int n,t;
char ch[202];
struct Pike
{
    int Map[202][202];
}a,ans;

Pike operator * (const Pike a,const Pike b)
{
    Pike c;
    memset(c.Map,0,sizeof(c.Map));
    for(int i=1;i<=n*9;i++)
        for(int j=1;j<=n*9;j++)
            for(int k=1;k<=n*9;k++)
                c.Map[i][j]=(c.Map[i][j]+a.Map[i][k]*b.Map[k][j]%Mod)%Mod;
    return c;
}

void Quick(Pike s,int t)
{
    while(t)
    {
        if(t&1)
            ans=ans*s;
        s=s*s;
        t>>=1;
    }
}

int main()
{
    scanf("%d%d",&n,&t);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=8;j++)
            a.Map[(i-1)*9+j][(i-1)*9+j+1]=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%s",ch+1);
        for(int j=1;j<=n;j++)
            if(ch[j]>'0')
                a.Map[(i-1)*9+ch[j]-(int)'0'][9*(j-1)+1]=1;
    }
    for(int i=1;i<=n*9;i++)
        ans.Map[i][i]=1;
    Quick(a,t);
    printf("%d",ans.Map[1][(n-1)*9+1]);
    return 0;
}

T3游戏

Description

windy学会了一种游戏。 对于1到N这N个数字,都有唯一且不同的1到N的数字与之对应。 最开始windy把数字按顺序1,2,3,……,N写一排在纸上。 然后再在这一排下面写上它们对应的数字。 然后又在新的一排下面写上它们对应的数字。 如此反复,直到序列再次变为1,2,3,……,N。 如: 1 2 3 4 5 6 对应的关系为 1->2 2->3 3->1 4->5 5->4 6->6 windy的操作如下


1 2 3 4 5 6


2 3 1 5 4 6


3 1 2 4 5 6


12 3 5 4 6


2 3 1 4 5 6


3 1 2 5 4 6


1 2 3 4 5 6


这时,我们就有若干排1到N的排列,上例中有7排。 现在windy想知道,对于所有可能的对应关系,有多少种可能的排数。

Input

一个整数,N。

Output

一个整数,可能的排数。

Sample Input

3

Sample Output

3

Hint

100%的数据,满足 1 <= N <= 1000 。

考场思路

没有去想,所以没有思路(理直气壮)。

正解

正解我认为这是玄学,题解写的十分清楚我就不乱扯了。

题解:

Code

#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;

LL n,sl,ans;
LL book[1010],zs[1010];
LL f[1010];

void Make_zs()
{
    for(LL i=2;i<=n;i++)
    {
        if(!book[i])
            zs[++sl]=i;
        for(LL j=i+i;j<=n;j+=i)
            book[j]=1;
    }
} 

int main()
{
    scanf("%lld",&n);
    Make_zs();
    f[0]=1;
    for(LL i=1;i<=sl;i++)
        for(LL j=n;j>=zs[i];j--)
            for(LL k=zs[i];k<=j;k*=zs[i])
                f[j]+=f[j-k];
    for(LL i=0;i<=n;i++)
        ans+=f[i];
    printf("%lld",ans);
    return 0;
}

T4windy数

Description

windy定义了一种windy数。
不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。
windy想知道,在A和B之间,包括A和B,总共有多少个windy数?

Input

两个整数,A B。

Output

一个整数,表示A~B中有多少个windy数。

Sample Input

1 10

Sample Output

9

Hint

100%的数据,满足 1 <= A <= B <= 2000000000 。
 

考场思路/正解

听别人说是数位DP,可我不会啊!于是我开始瞎搞,结果就错了。

考后改题,发现这还真是数位DP的模板题啊。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;

LL a,b,suma,sumb;
LL wz[11];
LL f[11][11];  //f[i][j]表示长度位i且最高位为j的windy数 

LL Work(LL x)
{
    LL sum=0,g=x,sl=0;
    while(g/10!=0)
    {
        wz[++sl]=g%10;
        g/=10;
    }
    wz[sl+1]=g;
    for(int i=1;i<=sl;i++)
        for(int j=1;j<=9;j++)
            sum+=f[i][j];
    sl++;
    for(int i=1;i<g;i++)
        sum+=f[sl][i];
    for(int i=sl-1;i>=1;i--)
    {
        for(int j=0;j<wz[i];j++)
        {
            if(abs(wz[i+1]-j)>=2)
                sum+=f[i][j];
        }
        if(abs(wz[i+1]-wz[i])<2)
            break;
    }
    return sum;
}

int main()
{
    scanf("%lld%lld",&a,&b);
    for(int i=0;i<=9;i++)
        f[1][i]=1;
    for(int i=2;i<=10;i++)
        for(int j=0;j<=9;j++)
            for(int k=0;k<=9;k++)
                if(abs(k-j)>=2)
                    f[i][j]+=f[i-1][k];
    printf("%lld",Work(b+1)-Work(a));
    return 0;
}

总结

今天考试,三题DP,一题矩阵乘法,其中有三题是省选难度的,你要我命啊!!

觉得自己打码速度不够快,考试时智商为0。

距 NOIp2019 还剩 91 天      祭

 
posted @ 2019-08-10 21:30  Thm-V  阅读(121)  评论(0编辑  收藏  举报