2020.4.1 模拟赛游记 & 题解
游记
反正这次比赛不知道怎么的就炸了。。。
\(\text{T1}\) 一看,弱智题。然后就切了。
\(\text{T2}\) 一看,一开始感觉是暴力枚举。
但是后来一想是鸡兔同笼 Dev-c++:那你还调试那么多次 然后就切了。
\(\text{T3}\) 进入之后,感觉自己人生巅峰了。
一共 \(6\) 题,前 \(3\) 题都是水题?
来到 \(\text{T4}\),发现不太简单。
应该是个大模拟,欺负我没玩过 \(\texttt{zuma}\) 是吧?(好下次把三国杀、植物大战僵尸都玩了来切题)
然后想了想,既然要支持删除,那就 \(\texttt{vector}\) 上!
结果中间发现会有连环效应。
就用一个变量指着中间插入东西的位置,然后一直在那个位置往两边删除就行了?
当时我对题意有一点点的误解,导致后来就 AC 了(???)
反正当时觉得不太行。
\(\text{T5}\) 一开始盲猜数学题。
这种拆分是数学题吧?
当时呢,就按照尽量让 \(x^k \leq n\) 且接近的贪心去贪了。
中间的二分因为精度炸了好几次,快速幂也不太行。。
调出来之后自认为对了,然后谔谔。。
来到 \(\text{T6}\),发现仍然不是很简单吧。。。
好像之前做过类似的题?
第一感觉:二分。
但是看看数据范围发现不是。
\(n^2 \log n\) 过不了,\(n \log n\) 怎么可能出 \(n \leq 3000\)?
然后就弄掉二分了。(赛后才知道,那玩意儿没有单调性,幸亏没用二分啊)
突然发现是个 \(O(n^2)\) 暴力?然后就切掉了。
这难度似乎不是单调增的吧
\(\text{T7}\),求最大全 \(0\) 子矩阵。
随便找一个网上板子就行
结果后来写 \(\texttt{dp}\) 写了半天才调试出来。
然后去洛谷 AC 掉模板题之后就开始咕了。。。
题解
\(T1\) ~ \(T3\) 水题,不管它了。(如果不会那你可以退役了)
\(T4\) 简要题意:
每次在数组中间插入一个数,只要 当前插入的这个数与左 / 右 形成 \(\geq 3\) 个相同的数,就消去它们。可能引起连环效应,但注意:如果不是与插入的数进行效应则不抵消;求最后的数组。
首先你要支持插入和删除,第一反应是链表。(毕竟链表快啊)
但是链表的细节很繁琐,本人又不太会用指针。 不会就是不会,为什么说不太会呢
然后就想了想,用 \(\texttt{vector}\) 应该可以。
中途突然发现样例和题目描述有点冲突。
万一我在 \(x\) 位置消除的时候,若干次后引起 \(x+1 , x+2 , x+3\) 相等,那么抵消吗?
当时盲猜抵消,感觉自己没法做。
但是 \(A\) 题了是真的,所以抵消!(谔谔)
可是为什么A题的只有我一个?(还不是因为别人不屑于做题吧)
(调试代码没有去,码风太丑,而且还用了 \(\text{goto}\),我码力太烂)
时间复杂度:\(O(n + m \log n)\).(数据太弱了,数组都能过;不过不想写太多细节吧)
实际得分:\(100pts\).
(下面给出的是我考试时的代码,如果有文字是注释;注释掉的英文是我原来的调试代码)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
vector<int>v;
int n,m,c,x;
inline void print(char c) {
putchar(c); putchar(' ');
for(int i=0;i<v.size();i++) printf("%d ",v[i]);
putchar('\n'); return;
}
inline void solve() {
v.insert(v.begin()+x,c); //插入
int zuo=x,you=x; bool f=0;
goto fin; //清奇的码风开始了
fin: {
/* while(1) {*/
// print('a'); printf("h %d %d %d\n",x,zuo,you);
for(int i=x-1;i>=0;i--)
if(v[i]!=v[i+1]) {zuo=i+1;break;}
else if(i==0) zuo=i; //往左统计相同
for(int i=(!f)?(x+1):x;i<v.size();i++) //f 用来表示当前是第一次还是其它;因为第一次 x,x+1,x+2 是抵消的
if(v[i]!=v[i-1]) {you=i-1;brak;}
else if(i+1==v.size()) you=i; //往右统计相同
// printf("b %d %d %d\n",x,zuo,you);
/* }*/ if(!f) { //第一次
if(you-zuo+1<3) {
// printf("c %d %d %d\n",x,zuo,you);
// print('d');
return;
} //小于 3 就结束
v.erase(v.begin()+zuo,v.begin()+you+1); //整个区间删除
x=zuo; zuo=x; you=x-1; //继续指向位置
} else{
if(you-zuo+1<3) {
// printf("e %d %d %d\n",x,zuo,you);
// print('f');
return;
}
v.erase(v.begin()+zuo,v.begin()+you+1);
x=zuo; zuo=x; you=x-1; //代码一模一样,是因为一开始我对题目有无解后来改的;其实可以直接合并
} /*print('g');*/ f=1; goto fin; //神奇的 goto ,自己跳往自己实现循环,好东西
}
}
int main(){
freopen("zuma.in","r",stdin);
freopen("zuma.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;i++) v.push_back(read());
while(m--) {
c=read(),x=read();
solve();/*for(int i=0;i<v.size();i++) printf("%d ",v[i]);
putchar('\n');*/
} /*for(int i=0;i<v.size();i++) printf("%d ",v[i]);
putchar('\n');*/
printf("%d\n",v.size());
return 0;
}
\(\text{T5}\),是防 \(\texttt{AK}\) 的(结果还真防着我了)
显然,用 \(\text{dp}\).
但是,如果线性推是不现实的。
我们注意到,\(f_{n,k}\) 只与 \(f_{i,k-1} (i | k)\) 有关,枚举因数即可。
这时就要用记忆化搜索(好东西!),其实记忆化搜索比 \(\text{dp}\) 灵活一点。
然后不断往下即可。
如果你听过 \(Q\) 老师讲,现在你懂了;否则你一脸懵逼。
好,用 \(f_{n,k}\) 表示将 \(n\) 分解成 \(k\) 个正整数乘积,最小的和。
那么:
时间复杂度:\(O(\sqrt{n})\).(实际上我个人不太认同,毕竟要一层层下去的;但是通过实践表明,时间复杂度就大概在这个级别。但是常数比较大的)
实际得分:\(100pts\).(不好意思,直接复制标程了)
#include <bits/stdc++.h>
using namespace std;
int n, k;
map<int, map<int, int> > f;
int F(int n, int k) {
if (k == 1) return n; //边界
if (f[n][k]) return f[n][k]; //记忆化
int ret = n + k - 1; //分解成 n 和一堆 1
for (int i = 2; i * i <= n; ++i)
if (n % i == 0)
ret = min(ret, min(F(i, k - 1) + n / i, F(n / i, k - 1) + i)); //转移
f[n][k] = ret;
return ret;
}
int main() {
scanf("%d%d", &n, &k);
printf("%d\n", F(n, k));
return 0;
}
\(\text{T6}\) 弱智题,直接暴力枚举,暴力验证。
时间复杂度:\(O(n^2)\).
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+1;
#define EPS INT_MAX
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,a[N],f[N];
int ans=EPS,ansk=EPS;
int slove(int k) {
memset(f,0,sizeof(f));
int sum=0,ans=0;
for (int i=1;i<=n-k+1;i++) {
if((a[i]+sum)&1) ans++,f[i]=1; //位运算有点恶心,&1 相当于 %2 (在这里)
sum=sum+f[i];
if(i-k+1>=1) sum=sum-f[i-k+1];
}
for (int i=n-k+2;i<=n;i++) {
if((a[i]+sum)&1) return -1;
if(i-k+1>=1) sum=sum-f[i-k+1];
} return ans;
}
int main() {
freopen("cow.in","r",stdin);
freopen("cow.out","w",stdout);
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for (int k=1,t;k<=n;k++){ //暴力枚举
t=slove(k);
if (t!=-1 && t<ans) ans=t,ansk=k;
} printf("%d\n",ansk);
return 0;
}
\(\text{T7}\) 又是唯一 \(\texttt{AC}\),真不明白,模板题这么难???
算法一
暴力枚举矩阵左上角,右下角,然后暴力求和。
时间复杂度:\(O(n^6)\).
实际得分:\(0pt\).
算法二
用二维前缀和优化算法一.
时间复杂度:\(O(n^4)\).
实际得分:\(0pt\).
算法三
对每一行,枚举左右端点(即线段),然后用此线段向上枚举高。
时间复杂度:\(O(n^3)\).
实际得分:\(0pt\).(如果你开 \(\texttt{O2}\) 没准可以卡一卡)
算法四
滚来看看这 洛谷模板题的题解
然后你直接复制过来就行了。(别忘记 \(\div 3\) 啊。。)
时间复杂度:\(O(n^2)\).
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,m,a[N][N],h[N][N];
int l[N][N],r[N][N],ans=0;
int main(){
freopen("pool.in","r",stdin);
freopen("pool.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) a[i][j]=read();
for(int j=1;j<=m;j++) r[0][j]=m+1; //右边
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) h[i][j]=(!a[i][j])?(h[i-1][j]+1):0; //向上
int t=0;
for(int j=1;j<=m;j++)
if(!a[i][j]) l[i][j]=max(l[i-1][j],t); //向左
else l[i][j]=0,t=j;
t=m+1; for(int j=m;j;j--)
if(!a[i][j]) r[i][j]=min(r[i-1][j],t);
else r[i][j]=m+1,t=j; //向右
for(int j=1;j<=m;j++) ans=max(ans,(r[i][j]-l[i][j]-1)*h[i][j]); //统计
} printf("%d\n",ans);
return 0;
}