关于我想了很久才想出这题咋做这档事 - 1

#0.0 目录


目录中以下题目皆可点击查看原题

#1.0 P2004 领地选择

不难想到,这里就是让在一个有 \(N \times M\) 个点的矩形中,找到有 \(C \times C\) 个点的正方形,使各点之和最大,那么很自然的就会想到二维前缀和
我们在读入点 \((i,j)\)时,第 \(i-1\) 行的数以及点 \((i,j)\) 左边的数都已被读入,所以我们可以在一个点输入时便算出他的前缀和,那么他的前缀和怎么算?如图:

这张图就知道了 \((i,j)\) 可以由 \((i-1,j)\)\((i,j-1)\) 两块构成,不过要注意两个点

  • 有一块矩阵我们重复加了,也就是 \((i-1,j-1)\) 这一块,所以我们要减去它
  • 我们这个矩阵是不完整的,由图可知我们还有一块深蓝色的没有加,也就是 \((i,j)\) 这一点,所以我们要再加上该点的值
    该点前缀和即为 DP[i][j]=DP[i-1][j]+DP[i][j-1]-DP[i-1][j-1]+map[i][j]

如下图,我们可以凭借前缀和算出以 \((i,j)\) 为正方形右下角时,所能得到的各点之和

S=DP[i][j]-DP[i-c][j]-DP[i][j-c]+dp[j-c][i-c],这里原理与上面相同,不过是将 \(1\) 变成了 \(c\)
之后比较所得答案大小,对现有答案进行更新,时间复杂度为 \(O(n \cdot m)\)

#include <iostream>
#include <cstdio>
#define SIZE 10010
using namespace std;

int n,m,c;
int ma[SIZE][SIZE],ans[3];

int main(){
    ans[0] = -0x3fffffff;
    scanf("%d%d%d",&n,&m,&c);
    for (int i = 1;i <= n;++ i)
      for (int j = 1;j <= m;++ j){
          scanf("%d",&ma[i][j]);
          ma[i][j] = ma[i - 1][j] + ma[i][j - 1] - ma[i - 1][j - 1] + ma[i][j];
          if (i >= c && j >= c){
              int s = ma[i][j] - ma[i - c][j] - ma[i][j - c] + ma[i - c][j - c];
              if (ans[0] < s){
                  ans[0] = s;
                  ans[1] = i - c + 1;
                  ans[2] = j - c + 1;
              }
          }
      }
    printf("%d %d",ans[1],ans[2]);
    return 0;
}

#2.0 P3916 图的遍历

最初的想法是dfs加记忆化,但遗憾并没有实现,在计算时会遇到错误,所以换一个思路。
我们要找从点 \(v\) 出发能到达的编号最大的点,显然,这个最大编号的点到点 \(v\) 之间显然是有路可走的,正着来不行,那么我们可否可以从反方向建图,从编号大的点 \(u\) 开始dfs,能遍历到的点所能达到的最大的点显然就是这个点 \(u\),对每个点进行标记,后面再次遍历时便不会再进行更新(显然后面的遍历起始点 \(i\) 的编号小于当前点 \(u\) 的编号),由于每个点只遍历 \(1\) 次,故时间复杂度为 \(O(n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#define SIZE 100011
using namespace std;

struct Edge{
    int to;
    int next;
};
Edge e[SIZE];

int n,m,tot;
int head[SIZE],vis[SIZE],ans,sum[SIZE];

inline void add(int v,int u){
    e[tot].to = v;
    e[tot].next = head[u];
    head[u] = tot;
    tot ++;
}

inline void dfs(int p,int st){
    if (sum[p])
      return;
    sum[p] = st;
    for (int j = head[p];j != -1;j = e[j].next){
	if (!sum[e[j].to])
          dfs(e[j].to,st);
    }  
}

int main(){
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for (int i = 0;i < m;i ++){
	int u,v;
	scanf("%d%d",&u,&v);
	add(u,v);
    }
    for (int i = n;i > 0;i --)
      dfs(i,i);
    for (int i = 1;i <= n;i ++)
      printf("%d ",sum[i]);
    return 0;
}

#3.0 P4017 最大食物链计数

看题面描述,不难想到以下几点:

  • 这是个有向无环图DAG
  • 我们要从入度为零的点开始查找
  • 每次要等一个点的入度为零后才可以将它入队,
  • 我们要求从所有入度为零的点到出度为零的点总路径数

显然,我们要用 拓扑排序 来做
假设有以下一个图:

由入度为零的点到点A有 \(a\) 条路径
由入度为零的点到点B有 \(b\) 条路径
根据加法原理,由入度为零的点到点C有 \(a+b\) 条路径

思路并不难,将入度为零的点入队,
之后对队头的点 \(i\) 进行遍历,同时将点 \(i\) 可达到的点 \(j\) 的入度减一,点 \(j\) 的答案数加上点 \(i\) 的答案数;如果点 \(j\) 的入度为零,将该点入队

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define SIZE 500010
#define Mod 80112002
using namespace std;

struct Edge{
    int from;
    int to;
    int next;
};
Edge e[SIZE];

int n,m,ans,tot;
int head[SIZE],out[SIZE],in[SIZE],sum[SIZE];

queue <int> q;

inline void add(int u,int v){
    e[tot].to = v;
    e[tot].next = head[u];
    head[u] = tot;
    in[v] ++;
    out[u] ++;
    tot ++;
}

int main(){
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for (int i = 1;i <= m;i ++){
	int u,v;
	scanf("%d%d",&u,&v);
	add(u,v);
    }
    for (int i = 1;i <= n;i ++)
      if (in[i] == 0){
	  sum[i] = 1;
	  q.push(i);
      }
    while (!q.empty()){
	int now = q.front();
	q.pop();
	for (int i = head[now];i != -1;i = e[i].next){
	    sum[e[i].to] += sum[now];
	    sum[e[i].to] %= Mod;
	    in[e[i].to] --;
	    if (!in[e[i].to])
	      if (!out[e[i].to]){
		ans += sum[e[i].to];
		ans %= Mod;
	      }
	    else
	      q.push(e[i].to);
	}
    }
    printf("%d",ans);
    return 0;
}

#4.0 P1038 神经网络

首先,根据题面,我们可以知道以下几点:

  • 每个图都是一个有向无环图DAG
  • \(C_i\) 的计算公式为 \(C_i=\sum\limits_{(j,i)\in E}W_{ji}C_j-U_i\),其中 \((j,i)\in E\) 为所有以 \(i\) 为终点的边,\(W_{ji}\) 为这条边的权值,\(U_i\) 为阈值
  • 要从入度为 \(0\) 的点开始向后计算,点 \(i\)\(C_i\) 要算出后才可以向后传递(注意只有当 \(C_i>0\) 时才会后传)
  • 我们要求每个出度为 \(0\) 的点的 \(C\)

关于第2点的公式,我们可以这么理解,每遍历到一条以 \(i\) 为终点的边,对 \(C_i\) 进行一次更新,然后使点 \(i\) 的入度减一,当点 \(i\) 的入度为 \(0\) 后,便可以用它向下传递信息,这样也同时满足了第三点
综上,我们很容易想到用 拓扑排序 来做这道题

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define SIZE 100010
using namespace std;

struct Node{ //每个点拥有的数值
    int c;
    int thre;
    int in;
    int out;
};
Node d[SIZE];

struct Edge{ //每条边拥有的数值
    int u;
    int v;
    int w;
    int next;
};
Edge e[SIZE];

queue <int> q;

int n,p,head[SIZE],tot,f;

inline void add(int u,int v,int w){ //加边,记得对点的入度和出度进行更新
    e[tot].u = u;
    e[tot].v = v;
    e[tot].w = w;
    e[tot].next = head[u];
    head[u] = tot;
    tot ++;
    d[u].out ++;
    d[v].in ++;
}

int main(){
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&p);
    for (int i = 1;i <= n;i ++){
	scanf("%d%d",&d[i].c,&d[i].thre);
	if (d[i].c > 0)//只有输入层的点一开始就有状态,即为入度为0的点
	  q.push(i);
	else
	  d[i].c -= d[i].thre; //阈值是迟早要减掉的,但输入层也有可能有阈值但不用减掉
    }  
    for (int j = 1;j <= p;j ++){
	int u,v,w;
	scanf("%d%d%d",&u,&v,&w);
	add(u,v,w);
    }
    while (!q.empty()){
	int now = q.front();
	q.pop();
	if (d[now].c <= 0) //只有C大于0才会后传信息
	  continue;
	for (int i = head[now];i != -1;i = e[i].next){ //边的遍历
    	    d[e[i].v].c += e[i].w * d[now].c; //更新终点的C值与入度
	    d[e[i].v].in --;
	    if (!d[e[i].v].in) //当该点入度为0时开始后传信息
	      q.push(e[i].v);
	}
    }
    for (int i = 1;i <= n;i ++) //注意输出格式
      if (!d[i].out && d[i].c > 0){
  	f = 1;
  	printf("%d %d\n",i,d[i].c);
      }
    if (!f)
      printf("NULL");
    return 0;
}

更新日志及说明

更新

  • 初次完成编辑 - \(2020.10.17\)
  • 增添了 #4.0 P1038 神经网络 的内容
    本文若有更改或补充会持续更新

个人主页

欢迎到以下地址支持作者!
Github戳这里
Bilibili戳这里
Luogu戳这里

posted @ 2020-10-17 19:03  Dfkuaid  阅读(89)  评论(0编辑  收藏  举报