这是某次校内赛的部分题解,之所以说“部分”,是因为①E题难度太大,其来源是cf中 *2500 的“Rectangle Painting 2”,我做不了,就将其替换成了 *2300 的 “Rectangle Painting 1”;②G题不想写题解了。
A.Tricks Device [HDU - 5294]
给出一张 n 个点,m 条边的无向图,每条边有边长 wi 。求①最少删去几条边使得从 1 号点到 n 号点的最短路数值变化;②最多删几条边使得从 1 号点到 n 号点的最短路数值不变。 n<=2000, m<=60000, 1<=wi<=100
首先说明,网上很多用网络流做,在我学会网络流之前,就先写一个不用网络流的方法吧。
对于②,只需找到从 1 到 n 的最短路中的最少路数就行了。设最少路数为 num,那么答案为 res2=m-num 。
对于①,稍有难度。首先求出 1 到所有点的单源最短路,借此我们可以得到从 1 到 n 的若干条具体的最短路径,将其再建一张图,这是一张从 1 到 n (或从 n 到 1 )的有向图。现在,我们只需求出在新图中最少删几条边就能使 1 和 n 不连通。思考后可知,必须要使整个路径在中间的某些点 u1、u2…处断开,使得从起点只能到达 u1、u2等点,而无法到达终点。而且 u1、u2 等点应无法相互到达(若从 u1 能到达 u2 ,那只删 u1 或只删 u2 不就行了吗)。于是便有了一个利用拓扑序求解的好办法:
首先,建立由最短路径组成的新图:
//我的这个图是从 1 到 n 的有向图
q2.push(n);
while(!q2.empty())
{
int u=q2.front(); q2.pop();
for(int p=Edge::h[u]; p; p=e[p].next)
{
int v=e[p].to;
if(d[u]==d[v]+e[p].w)
{
if(col[v]==0)
{
q2.push(v);
col[v]=1;
}
V[v].push_back(u);
}
}
}
然后求入度,为拓扑做准备
for(int i=1; i<=n; ++i)
{
if(V[i].size())
{
int sz=V[i].size();
for(int j=0; j<sz; ++j)
{
du[V[i][j]]++;
}
}
}
最后是最关键的拓扑环节,答案保存在 res1 中:
q2.push(1); int cur=0;
while(!q2.empty())
{
int u=q2.front(); q2.pop();
int sz=V[u].size();
cur-=du[u]; //关键步骤,意为“恢复所有到达v点的边”
for(int j=0; j<sz; ++j)
{
int v=V[u][j];
du[v]--;
if(du[v]==0) q2.push(v);
cur++; //关键步骤,意为“撤掉所有由v出发的边”
}
if(u!=n) res1=min(res1,cur); //关键步骤,更新答案
}
下面奉上完整代码:(话说这么一大坨代码划着好麻烦啊,有没有什么好办法呢?)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N=2010, M=60010, INF=1e9+7;
class Node
{
public:
int u, d;
Node(int u=0, int d=0):u(u),d(d) {};
bool operator < (const Node & t) const
{
return d>t.d;
}
};
int n, m;
int d[N], du[N], dis[N];
priority_queue<Node> Q;
queue<int> q2;
vector<int> V[N];
class Edge
{
public:
int to, next, w;
Edge(int to=0, int next=0, int w=0):to(to),next(next),w(w) {};
static int h[N], hn;
static void add(int,int,int);
}e[M<<1];
int Edge::hn, Edge::h[N];
void Edge::add(int u, int v, int w)
{
e[++Edge::hn]=Edge(v,Edge::h[u],w);
Edge::h[u]=Edge::hn;
}
void dijkstra(int s)
{
while(!Q.empty()) Q.pop();
for(int i=0; i<=n; ++i) d[i]=INF;
d[s]=0; Q.push(Node(s,0));
while(!Q.empty())
{
Node tmp=Q.top(); Q.pop();
if(tmp.d!=d[tmp.u]) continue;
int u=tmp.u;
// printf("u=%d\n", u);
for(int p=Edge::h[u]; p; p=e[p].next)
{
int v=e[p].to;
if(d[v]>d[u]+e[p].w)
{
d[v]=d[u]+e[p].w;
Q.push(Node(v,d[v]));
}
}
}
}
int col[N];
void solve()
{
for(int i=1; i<=n; ++i)
{
du[i]=0;
col[i]=0;
V[i].clear();
dis[i]=INF;
Edge::h[i]=0;
}
while(!q2.empty()) q2.pop();
Edge::hn=0;
int res1=INF, res2=INF;
for(int i=1, u, v, w; i<=m; ++i)
{
scanf("%d%d%d", &u, &v, &w);
Edge::add(u,v,w);
Edge::add(v,u,w);
}
dijkstra(1);
q2.push(n);
while(!q2.empty())
{
int u=q2.front(); q2.pop();
// printf("u=%d\n", u);
for(int p=Edge::h[u]; p; p=e[p].next)
{
int v=e[p].to;
// printf(" v=%d\n", v);
if(d[u]==d[v]+e[p].w)
{
if(col[v]==0)
{
q2.push(v);
col[v]=1;
}
V[v].push_back(u);
}
}
}
for(int i=1; i<=n; ++i)
{
// printf("i=%d\n", i);
if(V[i].size())
{
// printf("u=%d : ", i);
int sz=V[i].size();
for(int j=0; j<sz; ++j)
{
du[V[i][j]]++;
// printf(" %d", V[i][j]);
}
// puts("");
}
}
// for(int i=1; i<=n; ++i) printf("du[%d]=%d\n", i, du[i]);
dis[1]=0; q2.push(1); int cur=0;
while(!q2.empty())
{
int u=q2.front(); q2.pop();
int sz=V[u].size(); cur-=du[u];
for(int j=0; j<sz; ++j)
{
int v=V[u][j];
du[v]--;
if(du[v]==0)
{
q2.push(v);
}
cur++;
dis[v]=min(dis[v],dis[u]+1);
}
// printf("cur=%d\n", cur);
if(u!=n) res1=min(res1,cur);
}
res2=m-dis[n];
printf("%d %d\n", res1, res2);
}
int main()
{
while(~scanf("%d%d", &n, &m)) solve();
return 0;
}
B.搬寝室[HDU - 1421]
给 n 个数,要求从中选出 2*k 个数两两配对,每对数的代价为差的平方,求最小的代价和。(如 5 和 3 配对,代价为 (5-3)2=9 ) 2<=2*k<=n<=2000, 每个数为小于 215 的正整数
排序后,相邻地取数代价最小。考虑dp, f[i][j]表示前 i 个数中取了 j 对数的最小代价和,那么
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
−
1
]
[
j
]
,
f
[
i
−
2
]
[
j
−
1
]
+
(
a
[
i
]
−
a
[
i
−
1
]
)
2
)
f[i][j]=min(f[i-1][j],f[i-2][j-1]+(a[i]-a[i-1])^2)
f[i][j]=min(f[i−1][j],f[i−2][j−1]+(a[i]−a[i−1])2)
f[i][j] 如果是 2000*2000 的数组的话会MLE,因此我改成了 3*2000。
还有,此题多组数据。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=2010;
const LL INF=(1ll<<30)*2010;
int n, k;
int a[N];
LL f[3][N];
int main()
{
while(~scanf("%d%d", &n, &k))
{
for(int i=1; i<=n; ++i)
{
scanf("%d", a+i);
}
sort(a+1,a+n+1);
for(int i=0; i<3; ++i) for(int j=1; j<=n; ++j) f[i][j]=INF;
int t=0;
for(int i=2, o, p, q; i<=n; ++i)
{
o=i%3; p=(i-1)%3; q=(i-2)%3;
for(int j=1; j+j<=i; ++j)
{
f[o][j]=min(f[p][j],f[q][j-1]+(a[i]-a[i-1])*1ll*(a[i]-a[i-1]));
}
}
printf("%lld\n", f[n%3][k]);
}
return 0;
}
C.Doing Homework again[HDU - 1789]
现有 n 份作业,每份作业需 1 单位时间完成,第 i 份作业截止时间 ti,若截止后才完成,则扣去 ai 分,求最少扣去多少分。 n<=1000
扣分最少即得分最多呗。
可以看到,每份作业若是在截止日期后才做,那大可放在最后做。
首先按 ti 排序,然后按时间顺序做作业,若是当前日期 cur 大于当前作业日期 ti ,那么用这项作业把之前做了的作业中分值最少的那个替换掉(当然,前提是之前分值最少的那个的分值要小于现在这个的分值),这个操作可以用优先队列实现。因此复杂度 nlogn。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1010;
class Data
{
public:
int t, a;
static bool cmp(Data&x, Data&y)
{
return x.t==y.t?x.a>y.a:x.t<y.t;
}
}d[N];
int n;
priority_queue<int> Q;
void solve()
{
int sum=0, cnt=0;
scanf("%d", &n);
for(int i=1; i<=n; ++i)
{
scanf("%d", &d[i].t);
}
for(int i=1; i<=n; ++i)
{
scanf("%d", &d[i].a);
sum+=d[i].a;
}
sort(d+1,d+n+1,Data::cmp);
while(!Q.empty()) Q.pop();
int day=0;
for(int i=1; i<=n; ++i)
{
if(d[i].t>day)
{
Q.push(-d[i].a);
day++;
cnt+=d[i].a;
}else if(!Q.empty() && -Q.top()<d[i].a)
{
cnt+=Q.top(); Q.pop();
Q.push(-d[i].a); cnt+=d[i].a;
}
}
printf("%d\n", sum-cnt);
}
int main()
{
int T; cin>>T;
while(T--) solve();
return 0;
}
D.Almost Regular Bracket Sequence[CodeForces - 1095E]
有一个仅包含 ‘(’ 和 ‘)’ 的括号序列s。 如果括号序列的左右括号可以匹配,则是合法的,否则就是不合法的。(比如"()()“和”(())“是合法的;”)(“和”())"是不合法的)。 现在你只能改变某一个位置的括号(左括号变右括号或者右括号变左括号),请问有几个可以选择的位置。 1<=n<=106,n 为 s 长度
*1900 难度
对于这种括号匹配问题,一个套路就是以 ‘(’ 为 1,以 ‘)’ 为 -1,进行前缀和,此题也不例外。
求前缀和 sum 后,若 sum[n]=2或-2,才有可选择的位置。
若 sum[n]=2,则缺少一个 ‘)’ 。就从右到左寻找第一个 sum[i]=1 的位置,再从 i 向右找第一个 sum[j]=2 的位置,那么在 [j,n] 内 ‘(’ 的个数就是答案。
若 sum[n]=-2,对称做即可。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e6+10;
int n;
char s[N], t[N];
int a[N];
int main()
{
scanf("%d%s", &n, t+1);
for(int i=1; i<=n; ++i)
{
if(t[i]=='(') a[i]=a[i-1]+1;
else a[i]=a[i-1]-1;
}
if(a[n]==2)
{
for(int i=1; i<=n; ++i)
{
s[i]=t[i];
// printf("%d ", a[i]);
}
}
if(a[n]==-2)
{
for(int i=1; i<=n; ++i)
{
if(t[n-i+1]=='(') s[i]=')';
else s[i]='(';
if(s[i]=='(') a[i]=a[i-1]+1;
else a[i]=a[i-1]-1;
// printf("%d ", a[i]);
}
}
int res=0;
if(a[n]==2)
{
int w=0; bool ok=true;
for(int i=n; i>=1; --i)
{
if(a[i]<0)
{
ok=false;
break;
}
if(a[i]==1&&w==0) w=i;
}
if(ok)
{
for(int i=w+1; i<=n; ++i)
{
if(s[i]=='(') res++;
}
}
}
printf("%d\n", res);
return 0;
}
E.Rectangle Painting 1[CodeForces - 1098D]
给一个 n*n 的网格,每个格子用 ‘#’ 或 ‘.’ 代表,你每次可以选一个 h*w 的矩形将其全涂成 ‘.’ ,代价是 max(h,w),求将全部 ‘#’ 涂成 ‘.’ 需要的最小代价是多少。 1<=n<=50
*2300难度
易知,若一矩形 H*W (H>W) 的最大代价是H。若想减少代价,必需有某一列无 ‘#’。
考虑dp。 f[x1][x2][y1][y2] 是将 x∈[x1,x2],y∈[y1,y2] 内全变为 ‘.’ 需要的最小代价。
若 x2-x1>=y2-y1,则
f
[
x
1
]
[
x
2
]
[
y
1
]
[
y
2
]
=
m
i
n
(
x
2
−
x
1
+
1
,
f
[
x
1
]
[
i
−
1
]
[
y
1
]
[
y
2
]
+
f
[
i
+
1
]
[
x
2
]
[
y
1
]
[
y
2
]
)
,
第
i
列
无
′
#
′
f[x1][x2][y1][y2]=min(x2-x1+1,f[x1][i-1][y1][y2]+f[i+1][x2][y1][y2]),第 i 列无 '\#'
f[x1][x2][y1][y2]=min(x2−x1+1,f[x1][i−1][y1][y2]+f[i+1][x2][y1][y2]),第i列无′#′
若 x2-x1<=y2-y1,同理。
至于判断是否全为 ‘.’,预处理前缀和即可。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=50;
int n, sig;
char s[N+10];
int a[N+10][N+10], sum[N+10][N+10];
int f[N+2][N+2][N+2][N+2];
int cnt(int x, int y, int xx, int yy)
{
return sum[xx][y]-sum[x-1][y]-sum[xx][yy-1]+sum[x-1][yy-1];
}
int main()
{
cin>>n;
for(int i=1; i<=n; ++i)
{
scanf("%s", s+1);
for(int j=1; j<=n; ++j)
{
if(s[j]=='#') a[i][j]=1;
}
}
for(int i=1; i<=n; ++i)
{
for(int j=1; j<=n; ++j)
{
sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+a[i][j];
}
}
for(int x1=1; x1<=n; ++x1)
{
for(int x2=x1; x2>=1; --x2)
{
for(int y1=1; y1<=n; ++y1)
{
for(int y2=y1; y2>=1; --y2)
{
f[x1][x2][y1][y2]=max(x1-x2,y1-y2)+1;
if(x1-x2>=y1-y2)
{
for(int i=x2; i<=x1; ++i)
{
if(cnt(i,y1,i,y2)==0)
{
f[x1][x2][y1][y2]=min(f[x1][x2][y1][y2],f[i-1][x2][y1][y2]+f[x1][i+1][y1][y2]);
}
}
}
if(x1-x2<=y1-y2)
{
for(int j=y2; j<=y1; ++j)
{
if(cnt(x2,j,x1,j)==0)
{
f[x1][x2][y1][y2]=min(f[x1][x2][y1][y2],f[x1][x2][j-1][y2]+f[x1][x2][y1][j+1]);
}
}
}
}
}
}
}
printf("%d\n", f[n][1][n][1]);
return 0;
}
F.Maximum Value[CodeForces - 485D]
给出一个数列 a 由 n 个整数组成,求 ai%aj 的最大值(ai>aj)。 1<=n<=2*105, 1<=ai<=106
*2100难度
若 % ai 的最大值,只需对于所有 x=ai*k ,(k>=2,k*ai<=2*106),对每次数列中小于 x 的最大值 aj 求 aj%ai 即可。
预处理后,aj 可以 O(1) 得到。由于 1+1/2+1/3+1/4+…+1/n≈log2(n+1)+C,此题复杂度为 O(MlogM),M=2*106,也可以取 M 为 a 中的最大值。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6;
int n;
int a[N*2+10], pre[N*2+10];
int main()
{
scanf("%d", &n);
for(int i=1, x; i<=n; ++i)
{
scanf("%d", &x);
a[x]=1;
}
for(int i=2; i<=N*2; ++i)
{
if(a[i-1]) pre[i]=i-1;
else pre[i]=pre[i-1];
}
int res=0;
for(int i=2; i<=N; ++i)
{
if(a[i]==0) continue;
for(int j=i+i; j<=N*2; j+=i)
{
res=max(res,pre[j]%i);
}
}
printf("%d\n", res);
return 0;
}
G.Mister B and PR Shifts[CodeForces - 820D]
*1900难度
前面说过不想写题解了。
就放一个别人的题解吧。
https://www.cnblogs.com/TnT2333333/p/7091392.html
.
.
.
感谢客官看到这里。有什么问题欢迎指出!_(:3」∠❀)_