机房模拟题解

7月份的机房内模拟考,虽然题不是非常难,但是考得也不太好呢.....

T1 入阵曲

给你一个矩阵和一个常数k,问你有多少个子矩阵的和是k的倍数

数据范围:矩阵长宽不超过400,k<=1e6

我们可以首先想到n^4的做法,先记录二维的前缀和,然后枚举矩形的端点坐标,然后枚举其长和宽,这样可以拿到60分,再加上有15分特殊数据,可以靠暴力拿到75分以上

然后我们可以大概猜到正解是n^2logn或者是n^3,由于矩阵二分起来很不方便,我们就考虑n^3的做法

我们注意到,因为要是k的倍数,所以说只要模k为0的子矩阵就满足要求,因此我们只需要在前缀和中存下子矩阵的和对k的模数即可,我们又可以注意到,如果有一个矩阵和一个大矩阵模k意义下同余,则说明大矩阵减去小矩阵一定是k的倍数,如下图

于是我们只需要枚举起点坐标和长度或者宽度,然后用一个桶来存每一个子矩阵取余过后的数的数量,就可以n^3(其实是n*m^2或者m*n^2)处理这个问题了

代码实现如下

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<vector>
const int MAXN=405;
typedef long long ll;
ll ans;
int n,m,k;
int a[MAXN][MAXN];
ll s[MAXN][MAXN];
ll b[MAXN];
int t[1000005];
int vis[1000005],cnt;
int v[MAXN];
int vnum; 
void work(int x,int y)
{
    cnt++;
    vnum=0;
    std::memset(v,0,sizeof(v));
    for(int i=1;i<=m;i++)
	{
        b[i]=s[i][y]-s[i][x-1];
    }
    t[0]=1;
    vis[0]=cnt;
    v[++vnum]=0;
    for(int i=1;i<=m;i++)
	{
        b[i]+=b[i-1];
        int p=b[i]%k;
        if(vis[p]!=cnt)
		{
            vis[p]=cnt;
            t[p]=0;
            v[++vnum]=p;
        }
        t[p]++;
    }
    for(int i=1;i<=vnum;i++)
	{
        int x=v[i];
        ans+=1LL*t[x]*(t[x]-1)/2;
    }
}
int main()
{
	//std::freopen("rally.in","r",stdin);
	//std::freopen("rally.out","w",stdout);
    std::scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
	{
        for(int j=1;j<=m;j++)
		{
            std::scanf("%d",a[i]+j);
        }
    }
    for(int j=1;j<=m;j++)
	{
        for(int i=1;i<=n;i++)
		{
            s[j][i]=s[j][i-1]+a[i][j];
        }
    }
    for(int i=1;i<=n;i++)
	{
        for(int j=i;j<=n;j++)
		{
            work(i,j);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

T2 将军令

给你一颗树再给你一个定值k,树上的相邻点之间距离为一,你可以标记一些点,每一个被标记的点可以管理距标记距离为k以内的所有点,问你最少需要标记多少个点可以管理树上所有点

数据范围

k<=20

n<=1e5;

当k<=2的时候可以dp解决,但是当k=2的时候就已经有5个方程了.....所以对于更大的情况来说我们要思考一个通解

我们可以把树画出来,然后发现一个规律:在树上,深度更深的点更难被控制,因为深度浅的点可以被更多点控制,而深度更深的点则只能被更少的渠道控制,因此我们可以找到一个贪心策略:将所有点按照深度排序,从深度更深的点开始dfs,将其能控制到的所有点打上标记,再从剩余的点中寻找深度最深的,一直循环,直到所有点都被管理,这个贪心的策略正确性显然,时间复杂度O(n)

代码实现如下

#include<cstdio>
#include<algorithm>
const int MAXN=1e5+5;
int n,k,t;
inline int ABS(int x)
{
	return x>0?x:-x;
}
class Node
{
	public:
		int id;
		int dep;
}node[MAXN];
class Edge
{
	public:
		int nxt;
		int to;
}edge[MAXN<<1];
int head[MAXN];
int num=0;
bool vis1[MAXN];
bool vis2[MAXN];
void add(int from,int to)
{
	edge[++num].nxt=head[from];
	edge[num].to=to;
	head[from]=num;
}
void dfs1(int u,int f)
{
	for(int i=head[u];i;i=edge[i].nxt)
	{
		int v=edge[i].to;
		if(v==f)
		{
			continue;
		}
		node[v].dep=node[u].dep+1;
		dfs1(v,u);
	}
}
bool cmp(const Node &a,const Node &b)
{
	return a.dep>b.dep;
}
void solve(int u,int dis)
{
	vis1[u]=vis2[u]=1;
	for(int i=head[u];i;i=edge[i].nxt)
	{
		int v=edge[i].to;
		if(!vis2[v]&&dis)
		{
			solve(v,dis-1);
		}
	}
	vis2[u]=0;
}
int main()
{
	std::scanf("%d%d%d",&n,&k,&t);
	node[n].id=n;
	for(int i=1;i<=n-1;i++)
	{
		node[i].id=i;
		int u,v;
		std::scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	node[1].dep=1;
	dfs1(1,0);
	std::sort(node+1,node+1+n,cmp);
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		if(!vis1[node[i].id])
		{
			vis1[node[i].id]=1;
			solve(node[i].id,k<<1);
			ans++;
		}
	}
	std::printf("%d\n",ans);
	return 0;
}

T3 星空

难度爆炸的状压....

题意是给你一串01串,你可以将一定长度(该长度有m种,题目会给你)的灯泡0变1,1变0,问你全部搞成1的最少操作次数

dfs有16分,然后rand()运气够好可以再苟8分,如果贪心策略正确可以再拿12分23333333

数据范围

n(灯泡数量)<=4e4;

m<=64;

k<=8(k为熄灭的灯泡位置,其他所有灯泡默认为亮着)

我们发现k很小,所以我们可以试着将k状压下来

我们将每一个位置的灯泡状态与后一个位置差分,便会得到一个1(设熄灭为1,亮灯为0)的数量不超过16个的01串,然后我们会发现在原数组上将0、1取反就是在差分后的数组上更换其位置,而当两个一相撞的时候,就可以变成0(换句话说就是将两者都点亮),于是我们可以通过类似BFS的方式预处理出来亮亮灯泡之间最少需要多少步可以走到一起,然后状压得出最后的结果(将所有的1全部消除的最少步数)

这道题还有坑.....输入顺序是n、k、m不是n、m、k,而且样例的k、m相同你检查不出来(太tm恶意了、、、、)

代码实现不算复杂,但是非常混乱....如果不理清楚具体过程真的非常容易写晕......

// luogu-judger-enable-o2
#include<cstdio>
#include<queue>
#include<cstring>
const int NN=19;
const int MAXN=(1<<18)-1;
int n,m,k;
int nump;
int a[400005];
int b[400005];
int M[400005];
int poss[MAXN];
int dis[NN][NN];
int dist[MAXN];
int dp[MAXN];
bool vis[MAXN];
bool inv[MAXN];
int l[MAXN];
int dy[MAXN];
void BFS()
{
	std::memset(dis,0x3f,sizeof(dis));
	for(int i=1;i<=nump;i++)
	{
		std::memset(vis,0,sizeof(vis));
		std::memset(dist,0,sizeof(dist));
		std::queue<int>q;
		int src=poss[i];
		q.push(src);
		vis[src]=1;
		while(!q.empty())
		{
			int u=q.front();
			q.pop();
			for(int j=1;j<=m;j++)
			{
				int v=u+M[j];
				if(!vis[v]&&v<=n&&v>=0)
				{
					dist[v]=dist[u]+1;
				    q.push(v);
				    vis[v]=1;
	    			if(inv[v])
				    {
				    	dis[i][dy[v]]=dist[v];
				    }
				}
			}
		}
	}
}
void solve()
{
	dp[0]=0;
	for (int i=0;i<(1<<nump);i++)
    {
        int j=1;
        while (i&(1<<j-1))
		{
			j++;
		}
        for (int p=j+1;p<=nump;p++)
        {
            if (!(i&(1<<p-1)))
            {
            	dp[i|(1<<j-1)|(1<<p-1)]=std::min(dp[i|(1<<j-1)|(1<<p-1)],dp[i]+dis[j][p]);            	
            }
        }
    } 
}
int main()
{
	std::scanf("%d%d%d",&n,&k,&m);
	for(int i=1;i<=k;i++)
	{
		int pos;
		std::scanf("%d",&pos);
		a[pos]=1;
	}
	for(int i=1;i<=m;i++)
	{
		std::scanf("%d",M+i);
		M[i+m]=-M[i];
	}
	m=m<<1; 
	for(int i=0;i<=n;i++)
	{
		b[i]=a[i]^a[i+1];
		if(b[i]==1)
		{
			poss[++nump]=i;
			inv[i]=1;
			dy[i]=nump;
		}
	}
	std::memset(dp,0x3f,sizeof(dp));
	BFS();
	solve();
	printf("%d",dp[(1<<nump)-1]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Amuseir/article/details/81508679