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;
}