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)求最大值!
对了,赛后这道题调成了 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; }
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; }
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; }