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\) 个正整数乘积,最小的和。

那么:

\[f_{n,k} = \begin{cases} n , k = 1 \\ \min_{i|n} \min(f_{i,k-1} + \frac{n}{i} , f_{\frac{n}{i},k-1} + i) , k \not = 1 \\ \end{cases} \]

时间复杂度:\(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;
}

posted @ 2020-04-01 15:17  bifanwen  阅读(175)  评论(0编辑  收藏  举报