10-2国庆节第五场模拟赛题解

T1 seq:

序列2 (seq)

Description

给定个长度为 n 的数列 {a},初始时数列中每个元素 a_i 都不大于 40。你可以在其上进行若干次操作。在一次操作中,你会选出相邻且相等的两个元素,并把他们合并成一个元素,新的元素值为 \((旧元素值+1)\)。

请你找出,怎样的一系列操作可以让数列中的最大值变得尽可能地大?这个最大值是多少?

Input

输入文件第一行一个正整数 n,表示数列的长度。

接下来一行 n 个不大于 40 的正整数,表示这个数列 {a }。

Output

输出一个整数,表示经任意次合法操作后所有可能得到的数列中的最大值。

T1是一道DP,洛谷P3147,原题链接https://www.luogu.org/problemnew/show/P3147

考虑怎么设状态,很奇怪的是题目里面给出了初始数值全都不大于40,由数据范围可以知道,最后的答案ans不会超过max+logn,也就是近似最大值为58,那么让我们突破一下思维,将所谓的答案设成状态,也就是设\(f[i][j]\)表示从第i个位置出发,到第\(f[i][j]\)这个位置可以合并出j这个答案。

初始化就是\(f[i][a[i]]\)=i;

得到状态转移方程\(f[i][j]=f[i][f[i][j-1]][j-1]\)

很简单吧,还有一个要注意的地方,可以注意到每一次都是j由j-1推出,所以要先枚举j。

虽然不甚理解eolv的意思,但这应该是一个DP的注意的地方,还是要记住的。

#include<iostream>
#include<cstdio>
using namespace std;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
const int wx=262144;
int f[wx][59];
int n,ans;
int x;

int main(){
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++){
        x=read();
        f[i][x]=i+1;
    }
    for(int j=2;j<=58;j++){
        for(int i=1;i<=n;i++){
            if(!f[i][j])f[i][j]=f[f[i][j-1]][j-1];
            if(f[i][j])ans=max(ans,j);
        }
    }
    printf("%d\n",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

T2: sum

打表好题啊,eolv大佬讲这道题的时候总结了一下规律:

在关于数学的打表题中:首先应该想到几个重要的函数:
1)d(n)表示n的约束个数。

2)phi(n)表示小于等于n的与n互质的数的个数。(欧拉函数)

3)u(n)表示n的因子分布情况。(莫比乌斯函数)

4)id(n)即表示n本身。(废话啊喂,打表当然要输出n了啊)。。

考场上这道题找规律找了一个多小时(太菜了。。。),最后实在找不出针对10000次询问1e9的数据范围的O(1)做法,忐忐忑忑打了个70分做法,没想到居然A了,感谢数据感谢数据。

(OIES大法好啊!!!)

eolv讲过之后忽然感觉这题还是挺简单的,挺好证,那就再来证一遍吧。

对于f(n)

\(f(n)=n^2-\sum_{a=1,a<=n}\sum_{b=1,b<=n}[n|ab]\)

? =\(n^2-\sum_{a=1,a<=n}\sum_{b=1,b<=n}[n/gcd(a,n)|a*b/gcd(a,n)]\)

? =\(n^2-\sum_{a=1,a<=n}\sum_{b=1,b<=n}[n/gcd(a,n)|b]\)//因为在这时n/gcd(a,n)与a/gcd(a,n)是互质的,所以 a/gcd(a,n)对答案是没有贡献的

? =\(n^2-\sum_{a=1,a<=n}gcd(a,n)\)//这是比较难理解的一步,考虑b的取值范围是1到n所以上式的意思就是有多少个数属于1到n并且整除n/gcd(a,n),那么自然就是n/n/gcd(a,n)=gcd(a,n)了

? =\(n^2-\sum_{d|n}phi(n/d)*d\)//数论证明中非常常见的一步,枚举因子

对于g(n),我们将f(d)带入

\(g(n)=\sum_{d|n}f(d)\)

? =\(\sum_{d|n}d^2-\sum_{d|n}\sum_{p|d}phi(d/p)*p\)

? =\(\sum_{d|n}d^2-\sum_{d|n}(phi(d)*id(d))\)//后一个\(\sum\)可以看成是phi函数和id函数的狄利克雷卷积

? =\(\sum_{d|n}d^2-\sum_{d|n}1(n/d)*((phi(d)*id(d)))\)

? =\(\sum_{d|n}d^2-1(n)*phi(n)*id(n)\)

? =\(\sum_{d|n}d^2-id(n)*id(n)\)

? =\(\sum_{d|n}d^2-\sum_{d|n}n*(n/d)\)

? =\(\sum_{d|n}d^2-\sum_{d|n}n\)

很容易得出答案就是n的因数的平方和减去n*n的约数个数。

#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
ll g[2005];
int t;
ll n,ans;
ll work(ll now){
    ll re=0;//记录因子的平方和
    ll tmp=0;//记录因子个数
    for(int i=1;i*i<=now;i++){
        if((now%i==0)&&(i*i!=now)){
            re+=i*i;re+=(now/i)*(now/i);
            tmp+=2;
        }
        else if(i*i==now){
            re+=i*i;
            tmp++;
        }
    }
    return re-now*tmp;
}
int main(){
    freopen("sum.in","r",stdin);
    freopen("sum.out","w",stdout);
    scanf("%d",&t);
    while(t--){
        scanf("%lld",&n);
        ans=work(n);
        printf("%lld\n",ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

T3:frame

Description

你想送给 Makik 一个情人节礼物,但是手中只有一块方格纸。这张方格纸可以看作是一个由 H 行 W 列格子组成的长方形,不幸的是上面甚至还有一些格子已经损坏了。为了让这张破破烂烂的方格纸变得像个礼物的样子,你要从中剪出一个边长不小于 L 的方框,并且损坏的格子都不能被包含在这个方框中。这里,一个边长为 s(s≥3) 的方框指的是大小为 s×s 的正方形最外层的 4(s-1)) 个格子所构成的形状。

在动手剪方格纸之前,请你算一算,一共有可能剪出多少种不同的方框?

Input

输入文件第一行包含四个整数 H,W,L,P分别表示格子的总行数、列数、方框边长的最小限制和损坏格子的数量。

接下来 P 行,其中第 i 行包含两个整数 x_i, y_i,表示方格纸上第 x_i 行第 y_i 列处的格子已经损坏。

Output

输出一行一个整数,表示剪出不同的方框的方案数。

数据规模与约定

对于 20%的数据,H \(\leq\) 500, W$\leq$500

对于另 20% 的数据,P = 0。

对于另 20% 的数据,1 \(\leq\) P\(\leq\) 10。

对于 100%的数据,1 \(\leq\) H$\leq$4000, 1 \(\leq\) W \(\leq\) 4000, 3 \(\leq\) L \(\leq\) min{W, H}, 0 \(\leq\) P \(\leq\) 100000 1≤H≤4000,1≤W≤4000,3≤L≤min{W,H},0≤P≤100000。

其中很简单的是P=0的部分分,直接输出不解释
\[
\sum_{i=l}^{i<=min(n,m)}(n-i+1)*(m-i+1)
\]
还有前面数据范围比较小的20分直接暴力,我写的\(n^4\),大佬们都用前缀和优化到了\(n^3\),好吧,比他们低十分不亏。

下面来讲一下正解吧,鸣谢GMPotlc大佬,细心的讲解。

因为题目中问的是正方形的个数,那么考虑正方形的性质,很容易想到正方形的左上角的点和右下角的点是在同一条主对角线上的,那么就可以将主对角线当做我们的中间变量去枚举,然后找到每条主对角线上有多少合法的正方形,总的取一个和就行。

枚举对角线的操作:

    for(int x=n,y=1;y<=m;x==1?++y:--x){//可以说是非常秀了
        int len=0;
        memset(sum,0,sizeof sum);
        memset(edge,0,sizeof edge);
        memset(head,0,sizeof head);//每次拉出一条主对角线都是一次新的区间XJB操作,所以每次都要初始化
        num=0;
        for(int i=x,j=y;i<=n&&j<=m;i++,j++)len++;//当前是拉出一条主对角线
        。。。
        。。。
    }

那么考虑怎么统计每条对角线上合法的正方形,暴力枚举是一定不可以的,一定会T的。

又因为是求和,考虑用我们熟悉的数据结构来维护,又因为对于每个点,让它作为一个正方形的左上角点,那么对应合法的右下角点是在一个范围内的,所以很容易想到用树状数组维护和,因为要差分一下。

考虑当前枚举到这条对角线的第j个点,主要是三个操作:

一、我们枚举到的这个点,可以是作为一个正方形的右下角位置的,所以要在这里统计一下答案。

1)首先我们要更新这个点作为右下角会有多少个左上角与之对应,那么我们就需要提前记录一下这个点作为右下角合法的左上角。又因为左上角一定是先被枚举到的,所以我们在枚举到的点作为左上角操作时,就可以找到它所对应的合法右下角的区间,通过差分将这个区间进行修改。不过这里是需要延迟标记的,也就是只有当我们枚举到它对应的右下角开始的位置时,这个点才有答案,所以这时才更新。

可能上述的话不够清晰,但是还是要尽量理解。

具体的操作就是我们提前记录这个点作为右下角对应的左上角的点,即从右下角连向一个左上角,当没见到右下角时,返回去从左上角的位置开始加1。

又因为这个点无论是作为左上角还是右下角,都会有一个对应的合法区间,所以完全可以通过树状数组差分来进行区间修改。

for(int j=head[i];j;j=edge[j].nxt){
                int v=edge[j].to;
                update(v,edge[j].dis);
            }

2)已经更新了当前的树状数组前缀和,那么我们有知道当前这个点作为右下角对应的左上角合法的区间,所以直接区间查询即可。

if(f2[i+x-1][i+y-1] >= l)ans+=query(i-l+1)-query(i-f2[i+x-1][i+y-1]);

3)当前枚举到的点当然不能只作为右下角进行操作。那么现在将它作为左上角应该做什么呢?

前面在1)操作是已经提到了,我们需要处理出当前点作为左上角对应合法的右下角区间,标记好这个区间的开头和结尾,那么但我们枚举到这两个位置时,就可以返回去更改这个点对答案的贡献,也就是一次差分。

所以找到这两个点,通过从这两个点想当前点建边完成信息的处理(也可以成为标记的实现)。

eolv大佬把这个成为在每个点维护一个链表,不过经过GMPotlc大佬的教诲,我在这个部分是通过链式前向星来实现的。

上面的难点讲完,就还剩下一个细节,我们需要提前预处理出每个点最多可以向左上或者向右下最多延伸的长度。那么每一个点在这个对角线上所对应的另一个点的合法区间就有了,一端是由题中给出的l决定,一端就由刚刚所说的极限长度决定。

那就还要判断一下每个点对应的区间的合法性。

也就是如果一个区间的左端点大于等于右端点了,那么肯定是不行的。

code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int wx=4001;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
struct node{
    int x,y;
}a[wx];
struct e{
    int nxt,to,dis;
}edge[wx*2];
long long ans;
int head[wx],sum[wx],vis[wx][wx],Left[wx][wx],Right[wx][wx],up[wx][wx],down[wx][wx];
int f1[wx][wx],f2[wx][wx];
int n,m,p,l,x,y,z,num,tot;
void add(int from,int to,int dis){
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].dis=dis;
    head[from]=num;
}
void update(int pos,ll k){
    for(int i=pos;i<=n;i+=(i&-i)){
        sum[i]+=k;
    }
}
ll query(int x){
    ll re=0;
    for(int i=x;i>=1;i-=(i&-i)){
        re+=sum[i];
    }
    return re;
}
void pre(){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(!vis[i][j]){
                Left[i][j]=Left[i][j-1]+1;
                up[i][j]=up[i-1][j]+1;
            }
        }
    }
    for(int i=n;i>=1;i--){
        for(int j=m;j>=1;j--){
            if(!vis[i][j]){
                Right[i][j]=Right[i][j+1]+1;
                down[i][j]=down[i+1][j]+1;
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            f1[i][j]=min(Right[i][j],down[i][j]);//能够向右下方扩展的长度
            f2[i][j]=min(Left[i][j],up[i][j]);//能够向左上方扩展的长度
        }
    }
}
int main(){
    freopen("frame.in","r",stdin);
    freopen("frame.out","w",stdout);
    n=read();m=read();l=read();p=read();
    for(int i=1;i<=p;i++){
        x=read();y=read();vis[x][y]=1;
    }
    pre();
    for(int x=n,y=1;y<=m;x==1?++y:--x){
        int len=0;
        memset(sum,0,sizeof sum);
        memset(edge,0,sizeof edge);
        memset(head,0,sizeof head);
        num=0;
        for(int i=x,j=y;i<=n&&j<=m;i++,j++)len++;//当前是拉出一条主对角线
        for(int i=1;i<=len;i++){
            for(int j=head[i];j;j=edge[j].nxt){
                int v=edge[j].to;
                update(v,edge[j].dis);
            }
            if(f2[i+x-1][i+y-1] >= l)ans+=query(i-l+1)-query(i-f2[i+x-1][i+y-1]);
            if(f1[i+x-1][i+y-1] >= l)add(i+l-1,i,1);
            if(f1[i+x-1][i+y-1] >= l)add(i+f1[i+x-1][i+y-1],i,-1);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

具体细节还是不少的,要好好理解。

原文地址:https://www.cnblogs.com/wangxiaodai/p/9740796.html

时间: 2024-10-12 02:47:16

10-2国庆节第五场模拟赛题解的相关文章

10-4国庆节第七场模拟赛题解

10-4 国庆节第七场模拟赛题解 T1工厂 (factory) 水 #include<iostream> #include<cstdio> #define int long long using namespace std; inline int read(){ int sum=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0

10-3国庆节第六场模拟赛题解

T1 炮 (cannon) Description Makik 曾经沉迷于打麻将,热衷于点炮的他近日终于开始爱上了中国象棋.面对一个n×m的棋盘,他不禁陷入了思考:在这张棋盘上摆"炮",并且任意两个"炮"之间不会互相攻击的方案数究竟有多少呢? 说明:两枚炮可以互相攻击,当且仅当它们处在同一行或同一列上且恰好间隔一枚棋子,即"炮打隔山". 由于 Makik 记不住太大的数字,所以请告诉他答案对 999983 取模的结果. Input 输入文件包含一

10月15日模拟赛题解

10月15日模拟赛题解 A 树 Description 给定一棵 \(n\) 个节点的树,每个节点有两个参数 \(a,~b\),对于每个节点,求子树中参数为 \(b\) 的所有节点的 \(a\) 之和 Limitations \(100\%\) \(1 \leq b \leq n \leq 10^5,~a \leq 1000\) \(60\%\) \(1 \leq b,n\leq 1000\) \(30\%\) \(1 \leq b, n \leq 10\) Solution 对于 \(30\%

lzoi模拟赛题解

A题:签到题原题:mtoi 联赛 的A题定位:真.签到题(普及B题或者提高d1A题)考点:选手的基本数学能力思维难度:提高-代码难度:普及A题 题解:80%:暴力枚举100%:注意到(a xor b)<=(a+b),于是把所有的数异或起来即可. B题:送分题原题:[多省省队联测]d2A:皮配定位:一道联赛d1B题,考察了选手的基本功.送了选手70分.把70%的2种做法扩展可以得到正解考点:多种背包dp,计数思维难度:提高代码难度:提高+ 前面的几个数据可以暴力枚举解决.50%的数据:考虑dp.设

计蒜课 八月模拟赛题解

看见机房有大佬上周写了上面的普及信心赛 于是我康了康 8月的提高组模拟赛 9月的还没开始qwq 真的 有点难 主要是我先打开了T2 我再次 对自己的数学产生了怀疑 我现在还是不会写T2 T1 又又又又都错题了 下次重建图 尽量写vector 都写 邻接表 变量差不多的容易搞混 我这个同学变又写错了 T1 :https://nanti.jisuanke.com/t/41086 题目大意就是 一个有向图 删一个点 把与他直接和间接 相连的点 删掉 然后 求删掉所有点的最小最大代价 : 为了避免这个环

10.06 国庆节第九场模拟赛

密钥(key) Description 在这个问题中,一个密钥是指一个长度为\(3n\)的二进制序列,其中\(n\)是正整数. 序列的每一位从左往右依次被编号为\(1\)到\(3n\) ,一个密钥的权值是指数字不同的相邻位的个数再加上\(1\) .比如: \(000\) 的权值是 \(1\), \(011010100\) 的权值是 \(7\). 密钥可以被修改.确切地说,你可以不断地进行下面的操作:任选两个相邻的位,然后同时将它们取反.例如,可以通过一次操作把 \(000\) 修改为 110 .

【一场模拟赛?】

JSH师兄来讲课,于是也弄了场比赛给我们做...被虐... A.简单的图论题 嗯求一个拓扑图中每个点的可达点数量,n=10000 嗯记忆化搜索,但是有可能搜到重复的点,于是我们不搜索可达点数量,转成搜索可达点集,这个可以用一个bool数组记录. 于是STL有某种东西叫做bitset(长知识 // Problem#: 14938 // Submission#: 3794800 // The source code is licensed under Creative Commons Attribu

2017.07.10【NOIP提高组】模拟赛B组

Summary 今天题目总体不是难,但是分数很低,只有100+10+30,其中第二题还是以前做过的,第一题设计数论,而且以前做过同一个类型的题目,比赛推了很长时间.第三题时以前做过的原题,是贪心没学好啊!方法也不够周到,数据看得不仔细. Problem T1 可见点数 题目大意 我更改了一下,但是求的东西是一样的.已知有n*n个人在一个n*n网络的格点上,有个人在(1,1)的位置,问他能看到多少个人的脸,不包括自己. 想法 已知一个定理,说得笼统一点,(x,y)和(x+a,y+b)点连一条边,其

CPPU程序设计训练营清明天梯模拟赛题解

感谢大家今天来做题 比赛地址:http://202.206.177.79/contest/8 由于博主比较菜,没做完所有题目,这里暂时仅提供前两部分的题解. 为了节约篇幅,题目及数据描述不再赘述,如有需求,请移步OJ查看. 感谢大家的辛苦付出,但是从这次比赛的结果来看,前行之路还非常非常漫长呐. 我寂寞的时候,会害怕踏出第一步.不会想到要去做什么事,所以,可能没有发觉很多很多的东西吧.--<夏目友人帐> 第一阶段 L1-1 天梯赛座位分配 (20分) 通过率:2.56% \(\;\;\) 通过