1013练习赛

考后总结:

kzsn这次终于AC了两道题了!!!
感动,虽然与巨佬还有很大差距!
T1,其实我有小失误,我当时不确定自己的ST表写的对不对,所以不太敢写。
考场我去写了线段树求区间最值,按理说这样的复杂度O(nlognlogn),肯定过不去的!
不过呢,在补题的过程中,我还是把ST表给补了,并写了个博客来记忆!
居然还学了个“滚动数组式的ST表”,真不错。
T2,还是忘了打表找规律,而且连基本的组合数都没有理解(多半是当时做晕了!)
补题时倒是思路很清晰。。。
对了,从巨佬口中得知,像这样有倍数关系的东西,可以多往倍数方面想,而非因数。
T3,拉丁方阵,很好想的构造题,但当时似乎脑子有点晕,一直不太敢写上去,于是拿了将近1小时去证明。。。
不过还好,至少证明出来后,自己勇敢的写了上去。 T4,应该是一道找规律题,可惜自己没搞出来。 应该先从简单的形式:
1.没有障碍;2.只有一个障碍;3.两个障碍或多个障碍! 其实讲题的时候我没怎么听得懂,只好自己后来又慢慢推,事实证明自己还是能找到规律的,就是感觉思路每次都很不清晰,总要想很久。

T1 单词表

你要将 n 个单词填入Excel表格。
填写规律:按
照列的顺序从上到下一次填入单词,当一列填满后再开始填写下一列, Excel表格会根据每一列的单词长度动态调整当前列的宽度。 每一列的宽度与列中最长单词保持一致。 同时为了分隔两列单词,两列间会留一个字符长度的空白。 注意:除了最后一列,其他列必须填满。 由于电脑屏幕宽度有限,每一行最多只能有 w 个字符。 问,在宽度不超过 w 的前提下,高度最少为多少。 数据范围:
1<=n<=10^6, 1<=ai<=w<=10^9

这道题读题很关键啊,听说有巨佬读题读挂了。我也看了好久,才发现它的顺序就是输入顺序。

如果我们需要判断 i 行能不能行,其实是很好判断的。

由于高度为 i ,所以每一列分别为 [(k-1)*i+1, k*i],我们只需要在这 n/i 列每列都求最大值,即可知道宽度。

kzsn最开始还不太敢写,就是因为枚举的那个 n/i ,这样枚举是调和级数!

所以可以证明,这样枚举也就 O(n lnn),接下来就需要大约 O(1)求最大值!

ST表!!!

对了,赛后这道题调成了 32M,其实还是可以做的。

由于我们用ST表的区间跨度是逐渐上升的,所以ST表的前一维没必要记了。

只需要一遍求值,一遍更新ST表即可。跑的飞快!

#include<stdio.h>
inline int max(const int x,const int y){return x>y?x:y;}
#define re register int
#define LL long long
static char buf[1<<20], *p1, *p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<20, stdin), p1==p2)?EOF:*p1++)
inline int read(){
    int x=0;
    char c=gc();
    for(;c<'0'||c>'9';c=gc());
    for(;c>='0'&&c<='9';c=gc())x=(x<<1)+(x<<3)+c-'0';
    return x;
}
const int N=1e6+6;
int f[N];
signed main()
{
    int n=read(), w=read();
    for(re i=1;i<=n;++i) f[i]=read();
    for(re i=1, S=1; i<=n; ++i)
    {
        if(i>=(S*2)){
            for(re j=1;j<=n;++j)
            f[j]=j+S<=n?max(f[j], f[j+S]):f[j];
            S<<=1;
        }
        LL ret=0;
        int lst=1;
        for(re j=i;j<=n && ret<=1ll*w;)
        {
            ret+=(lst<=j-S+1)?max(f[lst], f[j-S+1]):f[lst];
            if(j!=n)ret++;else break;
            lst=j+1;
            j+=i;
            if(j>n)j=n;
        }
        if(ret<=w){printf("%d", i);return 0;}
    }
    return 0;
}
View Code

T2 序列与改写 NKOJ8700

一个数 x ,可以改变成 y ,当且仅当:
1. x % y == 0;
2. (x & y) == y;
给你一个序列 ai ,每一步从序列中任选一个可以被改变的数并改写。
经过若干次修改后,数列变得不能修改。问有多少种改写方式。
结果 mod 998244353。

数据范围:
1<=n<=2000; 1<=ai<=1000000;

遇到这种题,以后一定要打一下表,来看每个数最多会被改写多少次。

经过打表可以得到,一个数最多会被改写4次。

这道题使用dp。

我们设 dp[i][j] 表示将前 i 个数全部变得不能修改 用 j 步 的方案数。

如果第 i+1 个数我们用了 k 次才将它变得不能修改, 那么 dp[i+1][j+k] += dp[i][k] * cnt[i+1][k] * C[j+k][j]。

其中,cnt[i][j] 表示第 i 个数用 j 次操作变不能修改的方案数,这个可以提前预处理出。

C[n][m] 是组合数,从 n 个里面选 m 个的方案数。

组合数这里指的是:当前k步操作可以穿插在这j+k中的任意位置。

对了,大佬说了,像这种有倍数关系的东西,可以多往枚举倍数去想,而非去找一个数的因数。

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define LL long long
const int N=5e6+6, mo=998244353;
LL fac[N], inv[N];
inline LL getc(int x,int y)
{
    if(x<y)return 0;
    if(y==0||y==x)return 1;
    return fac[x]*inv[y]%mo*inv[x-y]%mo;
}
inline LL ksm(LL x, int y)
{
    LL ret=1;
    while(y)
    {
        if(y&1)ret=ret*x%mo;
        y>>=1;
        x=x*x%mo;
    }
    return ret;
}
int las[N], ed[N], nt[N], tt;
inline void add(int x,int y){ed[++tt]=y;nt[tt]=las[x];las[x]=tt;}
int cnt[2005][10];
LL dp[2005][8005];
inline void DFS(int x, int step, int id)
{
    int flag=0;
    for(re i=las[x];i;i=nt[i])
    {
        int v=ed[i];
        flag=1;
        DFS(v, step+1, id);
    }
    if(!flag)cnt[id][step]++;
}
signed main()
{
    int n;
    scanf("%d",&n);
    fac[0]=1;
    for(re i=1;i<=n*4;++i)
    fac[i]=fac[i-1]*i%mo;
    inv[4*n]=ksm(fac[4*n], mo-2);
    for(re i=4*n-1;i;--i)
    inv[i]=inv[i+1]*(i+1)%mo;
    
    for(re i=1;i<=1000000;++i)
    for(re j=2;i*j<=1000000;++j)
    if(((i*j)&i)==i)add(i*j, i);
    
    for(re i=1;i<=n;++i)
    {
        int x;
        scanf("%d",&x);
        DFS(x, 0, i);
    }
    dp[0][0] = 1;
    for(re i=0;i<=n;++i)
    {
        for(re j=0;j<=n*4;++j)
        if(dp[i][j])
        {
            for(re k=0;k<=4;++k)
            if(cnt[i+1][k])
            dp[i+1][j+k]=(dp[i+1][j+k]+dp[i][j]*cnt[i+1][k]%mo*getc(j+k, k)%mo)%mo;
        }
    }
    int ans=0;
    for(re i=0;i<=4*n;++i)
        ans=(ans+dp[n][i])%mo;
    printf("%d", ans);
    return 0;
}
View Code

 

T3 拉丁方阵 NKOJ8701

拉丁方阵是一类 n * n 的整数矩阵,矩阵的每一行每一列都是 1 ~ n 的排列。
对矩阵的操作:
每次选择一行一列,将这个“十”字形区域每个数加 1 或减 1.
现在给定两个拉丁方阵 A 和 B,请通过若干操作将 A 变成 B。

数据范围:
2<=n<=100

这道题的结论很好想,就是将AB矩阵对应位相减后,若某位置为负数,就加1,若为正数,就减1。

至于为什么呢,“读者自证不难”!

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define py pair<int,int>
vector<py>A, B;
int a[105][105], b[105][105], c[105][105];
signed main()
{
    int n;
    scanf("%d",&n);
    for(re i=1;i<=n;++i)
        for(re j=1;j<=n;++j)
            scanf("%d",&a[i][j]);
    for(re i=1;i<=n;++i)
        for(re j=1;j<=n;++j)
            scanf("%d",&b[i][j]);
    for(re i=1;i<=n;++i)
        for(re j=1;j<=n;++j)
            c[i][j]=a[i][j]-b[i][j];
    for(re i=1;i<=n;++i)
    for(re j=1;j<=n;++j)
    {
        if(c[i][j]>0)
        {
            while(c[i][j])
            {
                A.push_back(py(i, j));
                c[i][j]--;
            }
        }
        if(c[i][j]<0)
        {
            while(c[i][j])
            {
                B.push_back(py(i, j));
                c[i][j]++;
            }                    
        }
    }
    printf("%d\n", (int)A.size()+(int)B.size());
    for(py i:A)printf("%d %d +\n", i.first, i.second);
    for(py i:B)printf("%d %d -\n", i.first, i.second);
    return 0;
}

T4 寻路游戏 NKOJ8702

一个 n * m 的矩阵,有 k 个障碍物。
因此每一行以及每一列中最多只有  个障碍物。
同时,为了保证棋盘的连通性,每个障碍物周围相邻的 8 格都不会有其它障碍物。
求任意两个格子(不能是障碍物)作为起点终点的所有情况下最短路径的长度总和。
矩阵上每一步只能在上下左右四个方向中选择一个移动一格。

数据范围:
1<=n,m<=1000

这道题需要很长时间的推理。

problem1 :如果没有障碍物我们该怎么做?

solution1 : 由于这个距离是曼哈顿距离,所以我们可以把距离拆成 x 方向,和 y 方向来分别求,这个特别好写,O(n)。

problem2:有障碍会有什么影响?

solution2:这会导致一些点对的距离增加2,。

problem3:有一个障碍物怎么搞?

solution3:对于障碍物为中心的十字架形的地方会影响,其余无影响。所以跟solution1差不多得到一定答案,再加上一些最短路会增加2的点对。

problem4:很多障碍物怎么办?

solution4:会发现,当且仅当一连串障碍物是挨在一起的且递增或递减,才会导致不同行或列的点所需代价增加2。

这里我们可以O(k)算出这些代价

 注意要开longlong,交题前一定要严格检查long long

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define int long long

const int N=1005;
int mp[N][N], A[N], B[N];
int ans, k;
struct node{
    int x, y;
    bool operator<(const node&p)const{
        return y<p.y;
    }
}p0[N], p[N];

int cnt[N][2];
inline void solve(int n)
{
    sort(p+1, p+1+k);
    for(re i=1;i<=k;++i)
    {
        cnt[i][0]=n-p[i].x;
        cnt[i][1]=p[i].x-1;
    }
    for(re i=1;i<=k;++i)
    {//可结合自己画的图来看
        int j=i, sum=cnt[i][1];
        while(j<k && p[j].y+1==p[j+1].y && p[j].x<p[j+1].x)
        {
            j++;
            ans += sum * cnt[j][0] * 4;
            sum += cnt[j][1];
        }
        i = j;
    }
}
signed main()
{
    int n, m;
    scanf("%lld%lld%lld",&n,&m,&k);
     for(re i=1;i<=k;++i)
    {
        int x, y;
        scanf("%lld%lld",&x,&y);
        ans += ((x-1)*(n-x) + (y-1)*(m-y)) * 4;
        A[x]=B[y]=1;
        p0[i]=(node){x, y};
    }
    for(re i=1, tot=0, num=0; i<=n; ++i)
    {
        int t=m-A[i];
        ans += t*tot*2;
        num += t;
        tot += num;
    }
    for(re i=1, tot=0, num=0; i<=m; ++i)
    {
        int t=n-B[i];
        ans += t*tot*2;
        num += t;
        tot += num;
    }
// 坐标转换
for(re i=1;i<=k;++i)p[i]=(node){p0[i].x, p0[i].y}; solve(n); for(re i=1;i<=k;++i)p[i]=(node){p0[i].x, -p0[i].y}; solve(n); for(re i=1;i<=k;++i)p[i]=(node){p0[i].y, p0[i].x}; solve(m); for(re i=1;i<=k;++i)p[i]=(node){p0[i].y, -p0[i].x}; solve(m); printf("%lld", ans); return 0; }

 

posted @ 2021-10-14 19:24  kzsn  阅读(83)  评论(1编辑  收藏  举报