6.26考试总结(NOIP模拟10)[入阵曲·将军令·星空]

对于虚伪而言,真实的光明或许过于耀眼了

前言

这一次吧,考得特别烂,主要是这次考试想搞一下特殊性质,然后一不小心就因小失大弄巧成拙了下,下次注意吧。。

T1 入阵曲

暴力

思路

对于这个题的话,暴力的话55pts是没有问题的,无非是二维前缀和优化一下。

然后针对于特殊性质我们可以再骗到20pts,我们发现矩形是否整除 K 是与矩阵内的数字和矩形的边长是有关系的,接下来暴力扫就可以了。。

有一个比较遗憾的地方就是这个题考试的时候搞了1.5h,但是码最基础的暴力实际上只用了20min,然后对于特殊性质搞了挺长时间,主要是搞因数那一块思路错了,最后对于这个特殊性质草草地打了上面那个暴力就走了。。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e2+10;
int n,m,ans,temp,mod,h[N][N],z[N][N],s[N][N],t[N][N];
int cnt,ys[N];
bool flag;
void check()
{
	/*
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
			cout<<t[i][j]<<' ';
		cout<<endl;
	}
//	*/
	freopen("date.in","r",stdin);
//	freopen("date.out","w",stdout);
}
inline void work(int x,int y)
{
	for(int i=1;i<=x;i++)
		for(int j=1;j<=y;j++)
			if((t[x][y]-t[i-1][y]-t[x][j-1]+t[i-1][j-1])%mod==0)
				ans++;
}
inline void solve_1()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			h[i][j]=h[i][j-1]+s[i][j];
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			z[i][j]=z[i][j-1]+s[j][i];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			t[i][j]=t[i][j-1]+z[j][i];
//	check();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			work(i,j);
}
inline void solve_2()
{
	if(mod%temp==0)
		mod/=temp;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(i*j%mod==0)
				ans+=(n-i+1)*(m-j+1);
}
#undef int 
int main()
{
	#define int register long long
	#define ll long long
//	check();
	scanf("%lld%lld%lld",&n,&m,&mod);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%lld",&s[i][j]);
			temp=s[1][1];
			if(s[i][j]!=temp)
				flag=true;
		}
	if(flag)	solve_1();
	else	solve_2();
	printf("%lld",ans);
    return 0;
}

正解

思路

对于正解就是在先前暴力的思路上优化,发现在\(\bmod k\)的意义下相同的前缀和任意取两个枚举相减一定是 k 的倍数了。

因此我们枚举每一行以及他下面的行,然后再枚举列,对于不同的\(\bmod k\)余数记录并且更新就好了,最后注意清空就好了。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e2+10,M=1e6+10;
int n,m,ans,mod,cnt[M],tmp[N],z[N][N],s[N][N],t[N][N];
inline void init()
{
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			z[i][j]=z[i][j-1]+s[j][i];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			t[i][j]=(t[i][j-1]+z[j][i])%mod;
}
#undef int 
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld%lld%lld",&n,&m,&mod);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%lld",&s[i][j]);
	init();
	for(int i=0;i<n;i++)
		for(int j=i+1;j<=n;j++)
		{
			cnt[0]=1;
			for(int k=1;k<=m;k++)
			{
				tmp[k]=(t[j][k]-t[i][k]+mod)%mod;
				ans+=cnt[tmp[k]];
				cnt[tmp[k]]++;
			}
			for(int k=1;k<=m;k++)
				cnt[tmp[k]]=0;
		}
	printf("%lld",ans);
    return 0;
}

T2 将军令

暴力

思路

其实吧,这个题比上一个题还要遗憾。。

对于本题的暴力,直接dfs就好,但是考试的时候一开始想的是在树上直接进行的dfs,然后发现深度搜索好像压根就不行。。。

于是我们用将近1h换来了几乎0pts,然后考虑更改dfs思路。

想到一个比较妙的方法,我们不是在树上进行dfs,而是对于可以覆盖到的驿站,以及有小队的驿站的数量进行深搜,然后对于每一个没有过小队的点进行尝试。

  • 注意:一定要在加小队的同时,扩散可以覆盖到的驿站,并及时进行回溯,不然就会5pts(code)

然后我们考虑特殊性质,发现对于所有节点到1距离不超过2的情况我们直接在1节点加入小队,在有子节点的深度为1的节点处加小队就好了。

暴力+特殊性质共计50pts。

当然,如果你打过小胖守皇宫这个题的话,对于k=1的情况也是50pts(反正我是一点都不记得了。。)

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,M=N<<1,INF=1e9;
int n,m,ans=INF,task,dep[N],f[N][20];
vector<int> v[N];
bool vis[N],vis1[N];
struct Edge
{
	int tot,head[N],nxt[M],ver[M];
	void add(int x,int y)
	{
		ver[++tot]=y;
		nxt[tot]=head[x];
		head[x]=tot;
	}
}e;
void check()
{
	freopen("date.in","r",stdin);
//	freopen("date.out","w",stdout);
}
void dfs(int x,int fa)
{
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for(int i=0;f[x][i];i++)
		f[x][i+1]=f[f[x][i]][i];
	for(int i=e.head[x];i;i=e.nxt[i])
		if(e.ver[i]!=fa)
			dfs(e.ver[i],x);
}
int LCA(int x,int y)
{
	if(x==y)
		return x;
	if(dep[x]>dep[y])
		x^=y^=x^=y;
	for(int i=20;i>=0;i--)
		if(dep[f[y][i]]>=dep[x])
			y=f[y][i];
	if(x==y)
		return x;
	for(int i=20;i>=0;i--)
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	return f[x][0];
}
int dist(int x,int y)
{
	return dep[x]+dep[y]-2*dep[LCA(x,y)];
}
void dfs1(int tot,int cnt)
{
	if(cnt>n)
		return ;
	if(tot>=n)
	{
		ans=min(ans,cnt);
		return ;
	}
	if(cnt>=ans)
		return ;
	vector<int > vi;
	for(int i=1;i<=n;i++)
	{
		if(vis[i])
			continue;
		bool temp=vis1[i];
		vis[i]=vis1[i]=true;
		vi.clear();
		for(int j=1;j<=n;j++)
		{
			if(vis1[j])
				continue;
			for(int k=0;k<v[j].size();k++)
				if(vis[v[j][k]])
				{
					vis1[j]=true;
					tot++;
					vi.push_back(j);
					break;
				}
		}
		dfs1(tot+(!temp),cnt+1);
		tot-=vi.size();
		vis[i]=false;
		vis1[i]=temp;
		for(int j=0;j<vi.size();j++)
			vis1[vi[j]]=false;
	}
}
void solve_1()
{
	dfs(1ll,0ll);
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
		{
			int dis=dist(i,j);
			if(dis<=m)
			{
				v[i].push_back(j);
				v[j].push_back(i);
			}
		}
	dfs1(0ll,0ll);
}
void solve_2()
{
//	/*
	dfs(1,0);
	int flag=0;
	int sum=0;
	for(int i=2;i<=n;i++)
		if(dep[i]==2&&e.nxt[e.head[i]])
			sum++;
	ans=sum+1;
//	*/
//	ans=2;
}
#undef int 
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld%lld%lld",&n,&m,&task);
	for(int i=1,x,y;i<n;i++)
	{
		scanf("%lld%lld",&x,&y);
		e.add(x,y);
		e.add(y,x);
	}
	if(!m)	ans=n;
	else if(task!=2)	solve_1();
	else	solve_2();
	printf("%lld",ans);
    return 0;
}

正解

思路

消防局的设立亿些相似。

正解是贪心,首先对于深度从大到小排序,用dis数组记录距离这个点最近的有小队的驿站的数量。

如果一个点的dis大于k,那么我们直接在他的第k级祖先加入小队,然后更新深度更小的节点的dis值就好了。

最后统计一下dis为0的节点数就是答案了。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,M=N<<1,INF=1e9;
int n,m,ans,task,id[N],dep[N],fa[N],dis[N],f[N];
struct Edge
{
	int tot,head[N],nxt[M],ver[M];
	void add(int x,int y)
	{
		ver[++tot]=y;
		nxt[tot]=head[x];
		head[x]=tot;
	}
}e;
void dfs(int x,int fat)
{
	dep[x]=dep[fat]+1;
	fa[x]=fat;
	for(int i=e.head[x];i;i=e.nxt[i])
		if(e.ver[i]!=fat)
			dfs(e.ver[i],x);
}
bool comp(int x,int y)
{
	return dep[x]>dep[y];
}
#undef int 
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld%lld%lld",&n,&m,&task);
	fill(dis+0,dis+n+1,INF);
	for(int i=1,x,y;i<n;i++)
	{
		scanf("%lld%lld",&x,&y);
		e.add(x,y);
		e.add(y,x);
	}
	for(int i=1;i<=n;i++)
		id[i]=i;
	dfs(1ll,0ll);
	sort(id+1,id+n+1,comp);
	for(int i=1;i<=n;i++)
	{
		int cnt=0,x=id[i];
		if(dis[x]<=m)
			continue;
		f[0]=x;
		while(cnt<m&&f[cnt])
		{
			f[cnt+1]=fa[f[cnt]];
			cnt++;
			if(f[cnt])
				dis[x]=min(dis[x],dis[f[cnt]]+cnt);
		}
		if(!f[cnt])
			cnt=max(cnt-1,0ll);
		if(dis[x]>m)
		{
			dis[f[cnt]]=0;
			int fat=f[cnt],sum=0;
			while(fat&&sum<=m)
			{
				dis[fat]=min(dis[fat],sum);
				fat=fa[fat];
				sum++;
			}
		}
	}
	for(int i=1;i<=n;i++)
		if(!dis[i])
			ans++;
	printf("%lld",ans);
    return 0;
}

T3 星空

解题思路

对于暴力就不多讲了,骗一下\(ans \le 4\)的分就好了,多了也骗不到。。

正解思路非常妙,官方的题解上说需要差分,但是个人感觉和差分关系下不大,无非是处理一下开关状态不同的边界,然后记录罢了。

其实如果时间允许的话我们可以通过bfs求出任意一点与其他点之间变成统一状态的最小操作次数。

但是我们发现所有真正有用的点只有\(2\times k\)个,所以只要bfs这几个点求出到其他点的距离就可以了。

然后由于k的数据比较小,我们直接对于k进行状压就好了,暴力枚举每一个边界点然后更新f数组,最后的满状态的就是ans了。

code

#include<bits/stdc++.h>
using namespace std;
const int N=4e4+10,M=18,K=70.,INF=1061109567;
int n,m,cnt,k,dis[M][N],len[K],f[1<<M],tmp[M];
bool s[N];
queue<int > q;
void bfs(int pos,int val)
{
	dis[pos][val]=0;
	q.push(val);
	while(!q.empty())
	{
		int num=q.front();
		q.pop();
		for(int i=1;i<=m;i++)
		{
			if(num-len[i]>=0&&dis[pos][num-len[i]]>dis[pos][num]+1)
			{
				dis[pos][num-len[i]]=dis[pos][num]+1;
				q.push(num-len[i]);
			}
			if(num+len[i]<=n&&dis[pos][num+len[i]]>dis[pos][num]+1)
			{
				dis[pos][num+len[i]]=dis[pos][num]+1;
				q.push(num+len[i]);
			}
		}
	}
}
int get_first(int state)
{
	int sum=0;
	while(!(state&(1<<sum)))
		sum++;
	return sum;
}
int work(int state)
{
	if(f[state]!=INF)
		return f[state];
	if(!state)
		return 0;
	int pos=get_first(state);
	for(int i=pos+2;i<=cnt;i++)
		if(state&(1<<(i-1)))
			f[state]=min(f[state],work(state^(1<<pos)^(1<<(i-1)))+dis[pos+1][tmp[i]]);
	return f[state];
}
int main()
{
	scanf("%d%d%d",&n,&k,&m);
	memset(dis,0x3f,sizeof(dis));
	memset(f,0x3f,sizeof(f));
	for(int i=1,x;i<=k;i++)
	{
		scanf("%d",&x);
		s[x]=true;
	}
	for(int i=1;i<=m;i++)
		scanf("%d",&len[i]);
	for(int i=0;i<=n;i++)
		if(s[i]!=s[i+1])
			tmp[++cnt]=i;
	for(int i=1;i<=cnt;i++)
		bfs(i,tmp[i]);
	printf("%d",work((1<<cnt)-1));
	return 0;
}
posted @ 2021-06-28 16:56  Varuxn  阅读(81)  评论(0编辑  收藏  举报