T1
Description
给出一个\(N*N(N≤600)\)的非负矩阵\(B=(b_{ij})\),和一个\(1*N\)的非负矩阵\(C=(c_{ij})\)。\(A=(a_{ij})\)是一个\(1*N\)的\(01\)矩阵,令矩阵\(D=(d_{ij}) (A*B-C)*A^T\),则\(D\)是一个\(1*1\)的矩阵。要求构造\(A\)矩阵最大化\(D\)矩阵的元素值,输出得到的\(D\)矩阵。
Solution
- 化下式子就知道答案为:\(\sum_{i=1}^n A_i[(\sum_{j=1}^n A_jB_{ij})-C_i]\)。
- 这样的话,可以想象有一张\(N\)个点的完全图,第\(i\)个点的点权为\(C_i\),边\(\)的权为\(B_{ij}\)。那问题就转化为在这张图上找一个子图,使得子图内边权和减点权和最大。
- 可以yy成一种神奇的二元关系,然后转化为最小割模型。这里提供一种建图:对于点\(i\),我们从源点\(S\)向它连一条容量为\(\sum_{j=1}^n B_{ij}\)的边,从它向汇点\(T\)连一条容量为\(C_i\)的边;然后对于一个点对\(i≠j\),从\(i\)向\(j\)连一条容量为\(B_{ij}\)的边。这样用\(\sum B\)减去最小割即为答案。
上述建图法的正确性是比较显然的。我们假设一开始全选,\(ans=\sum B\);然后考虑选点\(i\)的代价是\(C_i\)(即让\(ans-=C_i\)),不选点\(i\)的代价是\(\sum_{j可不选} B_{ij}+\sum_{j必选}B_{ji}\)。显然这种代价和即为答案。实际上,我们只要建出一个满足这些代价的图即可(因而有无数种建图方法)。
Code
#include
#include
#define min(x,y) (x
T2
Description
- 一个平面直角坐标系上,有\(N(≤7000)\)个点,标号为1到N,其中第i个点的坐标为(x[i], y[i])。
- 求满足以下两个条件的点列{p[i]}的数目(假设{p[i]}的长度为M):
1) 对任意1 <= i < j <= M,必有y[p[i]] > y[p[j]];
2) 对任意3 <= i <= M,必有x[p[i-1]] < x[p[i]] < x[p[i-2]]或者x[p[i-2]] < x[p[i]] < x[p[i-1]]。 求满足条件的非空序列{p[i]}的数目,结果对一个整数\(Q(\in[1,1000000000])\)取模。
Solution
- 一道灵光一闪后就是普及难度的题目。
- 可以先将点按x坐标排序,然后DP。设f[i][0/1]表示考虑到第i个点,它的下一个点在它左边/右边的答案。那我们可以从小到大枚举i,再从i-1到1枚举一个j,比较y[i]和y[j],若y[i]
- 画个图就很明了了。由于我们一定是从y小的转移向y大的,所以是没有后效性的;而且由于我们按x坐标排了序,在用一个f转移前,已经考虑完了中间的(比如用f[i][0]转移f[j][1],我们已经考虑完了所有j
时间复杂度\(O(n^2)\)。
Code
- 画个图就很明了了。由于我们一定是从y小的转移向y大的,所以是没有后效性的;而且由于我们按x坐标排了序,在用一个f转移前,已经考虑完了中间的(比如用f[i][0]转移f[j][1],我们已经考虑完了所有j
#include
#include
#define P(x,y) (x+=y)%=Q
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=7010;
int n,Q,f[N][2],ans;
struct dot{int x,y;}p[N];
bool cmp(const dot&a,const dot&b) {return a.xp[i].y ? P(f[j][1],f[i][0]) : P(f[i][0],f[j][1]);
}
fo(i,1,n) P(ans,f[i][0]), P(ans,f[i][1]);
printf("%d",(ans-n+Q)%Q);
}
T3
Description
- 给出一棵\(N(≤100000)\)个带权点的有根树,每个点的孩子是从左到右有序的。一棵树的价值为所有叶节点的权值和减去每对相邻叶节点的路径上(不含那两个叶节点)的最大点权值。剪枝的本质是将某些结点的子树全部删去,使自己作为新的叶结点。
对树进行一些剪枝,使树的价值最大。剪枝的方式为:如果一个结点的孩子都是叶结点,就可以将它所有的孩子剪去。
Solution
- 也是一道水题。。。
没切真是耻辱 - 记lc(x)为从x出发,沿最左节点走下去形成的链,称为左链;rc(x)为右链。那每次肯定是有一对相邻的兄弟节点u、v,用一个rc(u)的答案更新lc(v)的答案。(当然反过来也可以,因为把整棵树左右翻转后的答案是不变的)
- 暴力转移即可,因为一个点至多只会在一条左链和一条右链上,因此是\(O(n)\)的。
转移时用一个单调队列优化一下。
Code
#include
#define max(x,y) (x>y?x:y)
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=11e4,inf=0x3f3f3f3f;
int n,w[N],t,p,pa[N],tot,to[N],ne[N],la[N],ls[N],rs[N],f[N],top,h[N],m[N],h1[N],h2[N],ans;
void work(int x,int y)
{
h1[0]=-inf;
for(top=0; x; x=rs[x])
{
h[++top]=x;
m[top+1]=max(m[top],w[x]);
h1[top]=max(h1[top-1],f[x]);
h2[top]=f[x]-m[top];
}
h2[top+1]=-inf;
fd(i,top,1) if(h2[i]m[k+1]) k++;
f[y]=w[y]+max(h1[k]-d,h2[k+1]);
if(dans) ans=f[x];
}
int main()
{
scanf("%d",&n);
fo(i,1,n)
{
scanf("%d%d",&w[i],&t);
fo(j,1,t)
{
scanf("%d",&p);
to[++tot]=p, ne[tot]=la[i], la[pa[p]=i]=tot;
if(j==1) rs[i]=p;
if(j==t) ls[i]=p;
}
f[i]=-inf;
}
dfs(1,1,1);
printf("%d",ans);
}