前言
可喜可贺,博主终于又AK了一次!尽管这套题比较简单。。
a星星
有n(1≤n≤400)个星星,每个星都有唯一的坐标(x,y)(1≤x,y≤N),请计算可以覆盖至少K(1≤K≤N)颗星的矩形的最小面积。矩形的边必须平行于X轴和Y轴,长度必须为正整数。星如果在矩形边上,也认为它是属于矩形内的。
(特别的,在本题中,星星是一个1*1的面,而不是一个点)
分析
说好的签到题呢?恕我(蒟蒻)直言,这道题有点难以签到啊。。
首先坐标很小(只有N),所以不用离散化。
然后就是考虑如何快速求一个矩形里面的星星的数量,显然可以用部分和来做。
最后就是暴力枚举以每一个点为起点,再枚举长,然后二分宽即可。
这种做法的时间复杂度略大,幸好评测机是香港记者。本题对于宽来说,可以使用单调队列优化时间复杂度。
程序
#include <iostream>
#include <algorithm>
#include <string.h>
#include <string>
#include <cmath>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int f[500][500],a[500][500],n,K,le,ri,mid,ans;
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d%d",&n,&K);
for (int i=1;i<=n;i++) {
int x,y;
scanf("%d%d",&x,&y);
a[x][y]++;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];
ans=n*n;
for (register int i=n;i>=1;--i)
for (register int j=n;j>=1;--j)
for (register int k=1;k<=i;k++)
{
if (f[i][j]-f[i-k][j]<K) continue;
le=0;ri=ans/k;
while (le+1<ri) {
mid=(le+ri)>>1;
if (f[i][j]-f[i-k][j]-f[i][j-mid]+f[i-k][j-mid]>=K) ri=mid;else le=mid;
}
if (f[i][j]-f[i-k][j]-f[i][j-ri]+f[i-k][j-ri]>=K) ans=ri*k;
}
printf("%d\n",ans);
return 0;
}
小结
这一题我一开始没有想到可以用部分和来求区间内星星的数量,说明本蒟蒻的基础还是很不行。
b战争
X国和Y国是死对头,X国有N个炮台, Y国有M个基地和K个发电站,炮台、基地、发电站所在的位置的坐标都是整数。Y国的每个发电站能对距离其L以内(包括L)的基地供电。X国的每个炮台都可以发射无限次,每次发射可以击毁一个基地或者一个发电站,消耗的能量为两者距离的平方,这里的距离是欧几里德距离。X国决定要摧毁Y国的所有基地,我们说Y国的某个基地被摧毁的条件是:基地本身被直接摧毁或者对其供电的所有发电站被击。
问X国摧毁Y国所有基地需要消耗的总能量最小是多少。
1 <= N、M、K <= 50. 1<=L<=2000。所有的坐标的范围:[-500,500]。所有的炮台、基地、发电站不会重复。数据保证每个基地至少有一个发电站为其供电。
分析
在这题中,由于炮台可以发射无限次,所以一旦计算了每个基地或发电站与最近炮台的距离,炮台就没有用了。
或许是最近做的网络流的题有点多吧,看到这种要么做要么不做的问题不是第一个想到背包而是想到网络流。
这题用网络流就是正解了。模型是最小割模型。
构图:①由于每个基地最后都要无法运行,所以每个基地到T连一条流量为E(破坏这个基地所需要的能量)的边。如果这条边被割了,表示这个基地被炸了。
②由于发电站也能炸,所以S到每个发电站也连一条流量为E(破坏这个发电站所需要的能量)的边。如果这条边被割了,表示这个发电站被炸了。
③如果某个发电站A能向某个基地B提供能量,那么A向B连一条边,表示如果A被炸了,B就少了一个发电站为它提供能量。(如果B只由一个发电站提供能量,则B就相当于被炸了)
程序
#include <iostream>
#include <algorithm>
#include <string.h>
#include <string>
#include <cmath>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
const int oo=10000000;
struct nd{
int x,y;
}a[60],b[60],c[60];
struct node{
int u,f;
node *ne,*neg;
}E[100000],*he[120];
int dl[120],bfn[120],n,m,p,ans=0,S,T,cnt,L;
int dis(nd a,nd b) {
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
void add(int x,int y,int f) {
cnt++;
E[cnt].u=y;E[cnt].f=f;E[cnt].ne=he[x];
he[x]=&E[cnt];E[cnt].neg=&E[cnt+1];
cnt++;
E[cnt].u=x;E[cnt].f=0;E[cnt].ne=he[y];
he[y]=&E[cnt];E[cnt].neg=&E[cnt-1];
}
void Init(){
scanf("%d%d%d%d",&n,&m,&p,&L);
for (int i=1;i<=n;i++) scanf("%d",&a[i].x);
for (int i=1;i<=n;i++) scanf("%d",&a[i].y);
for (int i=1;i<=m;i++) scanf("%d",&b[i].x);
for (int i=1;i<=m;i++) scanf("%d",&b[i].y);
for (int i=1;i<=p;i++) scanf("%d",&c[i].x);
for (int i=1;i<=p;i++) scanf("%d",&c[i].y);
}
void pre(){
S=0;T=110;
for (int i=0;i<=T;i++) he[i]=NULL;
for (int i=1;i<=m;i++)
{
int s=oo;
for (int j=1;j<=n;j++)
s=min(s,dis(b[i],a[j]));
add(i+50,T,s);
}
for (int i=1;i<=p;i++)
{
int s=oo;
for (int j=1;j<=n;j++)
s=min(s,dis(c[i],a[j]));
add(S,i,s);
}
for (int i=1;i<=p;i++)
{
for (int j=1;j<=m;j++)
if (sqrt(dis(c[i],b[j]))<=L) add(i,j+50,oo);
}
}
bool bfs(int S,int T) {
int le=1,ri=1;dl[1]=S;
memset(bfn,0,sizeof(bfn));
bfn[S]=1;
while (le<=ri) {
int u=dl[le];
for (node*ne=he[u];ne;ne=ne->ne) {
if (bfn[ne->u]>0||ne->f==0) continue;
ri++;dl[ri]=ne->u;bfn[ne->u]=bfn[u]+1;
}
le++;
}
return bfn[T]!=0;
}
int dfs(int k,int f) {
if (k==T) return f;
int ret=0;
for (node*ne=he[k];ne&&f!=0;ne=ne->ne) {
if (bfn[ne->u]<=bfn[k]||ne->f==0) continue;
int t=dfs(ne->u,min(ne->f,f));
ne->f-=t;ne->neg->f+=t;ret+=t;f-=t;
}
if (f!=0) bfn[k]=-1;
return ret;
}
int main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
Init();
pre();
for (;bfs(S,T);) {
ans+=dfs(S,oo);
}
printf("%d\n",ans);
return 0;
}
小结
比较经典的网络流,甚至不用拆点什么的乱七八糟的东西,理解题意发现炮台是没用的就好。
c染色树
一棵共含有X个结点的树,结点编号1至X,根结点编号是1。有Y种不同的颜色,颜色编号从1至Y。
现在给每个结点都染上一种颜色,整颗树染色后满足:
1、对于编号是i的颜色,整颗树当中,至少有一个结点被染成了颜色i。
2、根结点必须被染成1号颜色,而且整颗树当中,恰好要有Z个结点被染成1号颜色。
染色过程结束后,现在要计算染色的总代价,总代价等于每一条边的代价之和,那么怎么计算一条边的代价呢?
不妨设结点a与结点b之间有一条边,该边的权重是W。
1、如果结点a和结点b最终被染成不同的颜色,那么a与b之间的那条边的代价等于0。
2、如果结点a和结点b最终被染成相同的颜色,那么a与b之间的那条边的代价等于W。
现在的问题是:在满足上述要求的前提下,染色树最小的总代价是多少?如果无法完成染色的任务,输出-1。
分析
在这题中,可以很容易的分析到①只有颜色是1的节点与其他节点是不同的。换一种说法就是除了1以外的节点都是相同的。②颜色的数量是2的情况和其他情况是不同的。因为如果颜色数量只有2种,则每一个点的颜色要么是1要么是2,所以说对于一个点,假如ta的颜色是1,则与ta连接的点有两种情况,其中1是要产生权值的,2是不会产生权值,反之亦然。
但如果颜色的数量不止两种,那么对于一个点,假如ta的颜色是1,和颜色数量是2的是差不多的。但如果ta的颜色不是1(比如是2吧),则与ta连接的点有三种情况,2,1,和其他。可以贪心出与ta连接的点不可能是2,把它代替成“其他”会更优。
总结出这些性质,就可以开始解本题了。这题是最优值问题,又是一棵树,所以可以往DP方面去想。先想颜色数不是2的情况吧。
首先有一维阶段为节点,这是一定要的。题目的限制是1的数量,所以多记一维阶段是包括该节点所在的子树1的数量。由于当前节点的颜色会影响它父亲的取值,所以要再记录当前节点的颜色。这点的父亲节点的颜色也会影响到当前节点的颜色的取值,所以我分了f和g分别表示该点父亲不是1或该点父亲是1的最优解。由于这题节点间状态不止1维,所以我使用了大神们常用的左儿子右兄弟表示法来减少我的思维复杂度和程序复杂度。
接着就是找转移式了,具体可以看程序。
对于这种方法来说,颜色数是2的和颜色数是其他的稍改细节就可以了。
#include <iostream>
#include <algorithm>
#include <string.h>
#include <string>
#include <cmath>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
using namespace std;
const int oo=40000000;
vector <int> e[320];
vector <int> v[320];
int f[320][320][2],g[320][320][2],ls[320],rs[320],w[320],n,c,s;
void dfs(int k,int fa) {
int l=e[k].size(),lu;
for (int i=0;i<l;i++)
if (e[k][i]!=fa)
{
int u=e[k][i];w[u]=v[k][i];
if (ls[k]==0) ls[k]=u;else rs[lu]=u;
dfs(u,k);lu=u;
}
}
void dfs2(int k) {
if (k==0) return;
dfs2(ls[k]);dfs2(rs[k]);
f[k][0][0]=f[ls[k]][0][0]+f[rs[k]][0][0];
g[k][0][0]=g[rs[k]][0][0]+f[ls[k]][0][0];
f[k][0][1]=g[k][0][1]=oo;
for (int i=0;i<=s;i++){
f[k][i][0]=f[k][i][1]=g[k][i][0]=g[k][i][1]=oo;
for (int j=0;j<i;j++)
{
f[k][i][0]=min(min(f[ls[k]][j][0],f[ls[k]][j][1])+min(f[rs[k]][i-j][0],f[rs[k]][i-j][1]),f[k][i][0]);//父亲节点不是1,当前节点也不是1.
f[k][i][1]=min(min(g[ls[k]][j][0],g[ls[k]][j][1])+min(f[rs[k]][i-j-1][0],f[rs[k]][i-j-1][1]),f[k][i][1]);]);//父亲节点不是1,当前节点是1.
g[k][i][0]=min(min(f[ls[k]][j][0],f[ls[k]][j][1])+min(g[rs[k]][i-j][0],g[rs[k]][i-j][1]),g[k][i][0]);//父亲节点是1,当前节点不是1.
g[k][i][1]=min(min(g[ls[k]][j][0],g[ls[k]][j][1])+min(g[rs[k]][i-j-1][0],g[rs[k]][i-j-1][1])+w[k],g[k][i][1]);//这就是父亲节点和当前节点都是1的情况,记得要加上权值
}
f[k][i][0]=min(f[k][i][0],min(f[ls[k]][i][0],f[ls[k]][i][1])+min(f[rs[k]][0][0],f[rs[k]][0][1]));
g[k][i][0]=min(g[k][i][0],min(f[ls[k]][i][0],f[ls[k]][i][1])+min(g[rs[k]][0][0],g[rs[k]][0][1]));
}
}
void dfs3(int k) {
if (k==0) return;
dfs3(ls[k]);dfs3(rs[k]);
f[k][0][0]=f[ls[k]][0][0]+f[rs[k]][0][0]+w[k];
g[k][0][0]=g[rs[k]][0][0]+f[ls[k]][0][0];
f[k][0][1]=g[k][0][1]=oo;
for (int i=0;i<=s;i++){
f[k][i][0]=f[k][i][1]=g[k][i][0]=g[k][i][1]=oo;
for (int j=0;j<i;j++)
{
f[k][i][0]=min(min(f[ls[k]][j][0],f[ls[k]][j][1])+min(f[rs[k]][i-j][0],f[rs[k]][i-j][1])+w[k],f[k][i][0]);//父亲节点是2,当前节点也是2.
f[k][i][1]=min(min(g[ls[k]][j][0],g[ls[k]][j][1])+min(f[rs[k]][i-j-1][0],f[rs[k]][i-j-1][1]),f[k][i][1]);
g[k][i][0]=min(min(f[ls[k]][j][0],f[ls[k]][j][1])+min(g[rs[k]][i-j][0],g[rs[k]][i-j][1]),g[k][i][0]);
g[k][i][1]=min(min(g[ls[k]][j][0],g[ls[k]][j][1])+min(g[rs[k]][i-j-1][0],g[rs[k]][i-j-1][1])+w[k],g[k][i][1]);
}
f[k][i][0]=min(f[k][i][0],min(f[ls[k]][i][0],f[ls[k]][i][1])+min(f[rs[k]][0][0],f[rs[k]][0][1])+w[k]);
g[k][i][0]=min(g[k][i][0],min(f[ls[k]][i][0],f[ls[k]][i][1])+min(g[rs[k]][0][0],g[rs[k]][0][1]));
}
}
int main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
scanf("%d%d%d",&n,&c,&s);
if (c+s>n+1) {printf("-1\n");return 0;}
for (int i=1;i<n;i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
e[x].push_back(y);e[y].push_back(x);
v[x].push_back(z);v[y].push_back(z);
}
w[1]=0;
for (int i=1;i<=s;i++) f[0][i][0]=f[0][i][1]=g[0][i][0]=g[0][i][1]=oo;
f[0][0][1]=g[0][0][1]=oo;
dfs(1,1);
if (c!=2) dfs2(1); else dfs3(1);
printf("%d",min(g[1][s][1],f[1][s][1]));
return 0;
}
小结
这题是经典树形DP,脚踏实地的分析就不会错了。
总结
这套题不难,有5个人AK了,所以也没什么好骄傲的。。