T1
DP
Problem 1 :第一题(a.cpp)
题目描述
一个序列上共有n个点,两两之间形成一条线段,任意选择其中k条线段,使得这k条线段的长度和最小(k条线段端点不能重合)。
输入格式
第一行两个整数n k
接下来n行每行一个整数表示每个点在序列上的坐标 。
输出格式
一行一个整数表示这个最小的和。
样例输入
5 2
1
3
4
6
12
样例输出
4
数据范围及提示
对于30%数据
对于60%数据
对于100%数据
A:哇,好水啊。
B:哇,是啊。
A:那还把我们叫出来干嘛。
B:走人走人。
END.
由于小A小B不想分析水题(QAQ),
我就来口糊一下~
很明显可以看出只能取相邻点组成的线段,
而且不能取相邻线段(端点不能重复)
像不像没有上司的舞会?
不过更简单,在链上了。
每条线段选与不选都会影响后面的状态,
所以选和不选的答案都记录下来,
怎么区分?
0 or 1 !
如果这条线段选了,上一条只能是不选,答案加上这条线段的长度。
这条不选,上一条选和不选都有可能。
一直递推。
设计状态:
f[i][j][0/1]:到第i条,选了j条,第i条选或不选的min
根据上面说的,递推式如下:
f[i][j][1] = f[i - 1][j - 1][0] + len[i];
f[i][j][0] = min(f[i - 1][j][0],f[i - 1][j][1]);
我们发现第i层一定是由i - 1层转移过来的,滚动了吧。
T1就可以A掉了。
Codes:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 10000 + 10;
int n,k,cn;
int f[5000 + 10][2],g[5000 + 10][2],len[N],a[N];
int main(){
scanf("%d%d",&n,&k);
memset(f,0x3f3f3f3f,sizeof(f));
memset(g,0x3f3f3f3f,sizeof(g));
for(int i = 1;i <= n;++ i){
scanf("%d",&a[i]);
if(i > 1){
len[++ cn] = a[i] - a[i - 1];
}
}
f[1][1] = len[1];
f[0][0] = f[0][1] = g[0][0] = g[0][1] = 0;
for(int i = 1;i <= cn;++ i){
for(int j = 1;j <= k;++ j){
f[j][1] = g[j - 1][0] + len[i];
f[j][0] = min(g[j][0],g[j][1]);
}
for(int j = 1;j <= k;++ j){
g[j][1] = f[j][1];
g[j][0] = f[j][0];
}
}
cout << min(f[k][0],f[k][1]) << '\n';
return 0;
}
你以为这样就结束了?
Don't 天真。
原题可不是这么好欺负的。
贪心
题目链接:http://codevs.cn/problem/1615/
A:诶,这题似曾相识?
B:这不就是上面那个么?
A:数据数据!
60%的输入数据满足n≤10 000。
B:哇……这怎么办……dp%60。
A:还记得上次的模拟题么?(好吧并没有写:http://codevs.cn/problem/2033/)
B:贪心?对哦,那个题记得dp只能过%60,好像哦。
A:那个题是当选和不选都会对答案做出贡献时,也是都记录答案,贪心的话,怎么都记录答案呢?
B:当时是用堆顶处理出的答案记录一下,然后把删掉的数再扔进去一遍!!
A:这样就可以都记录下来了!用贪心的方式。这题类似。
END.
哇哦……又一个高级贪心QAQ
容易想到的做法是每次选最小的线段,
但明显在很多情况下是不对的,
比如样例,选了最小的反而不能选相比6 + 1更优的2 + 2,
所以想到dp,
要把选和不选最小的答案都存起来,
类似邮票那个题,
最小值用堆来维护,
每次取出堆顶更新答案,
如果选了堆顶,它两边的就不能选啦,
用双向链表维护点之间的关系(用来找到它两边的且没有被删除的点),
就把这个点的pre,nxt更新成隔一个相邻的,
再把与他相邻的点的pre,nxt也更新成与这个点隔一个相邻的。
举个例子:
序列:d b a c e
堆顶是a,然后取出a,更新答案(ans += 堆顶),
放进去一个b + c - a,(想想为什么减a)
然后将a的nxt,pre更新成d,e,
将b到c看成一个点,nxt,pre也更新成d,e。
(说得好乱啊啊啊,还是看代码更清晰QAQ)
代码是这样:
A[++ cnt] = u.v;
int tmp1 = pre[u.pos],tmp2 = next[u.pos];
next[pre[tmp1]] = cnt;
pre[next[tmp2]] = cnt;
pre[cnt] = pre[tmp1];
next[cnt] = next[tmp2];
(把b到c看成一个点,这个新加进去的点的编号是cnt)
将它相邻的点标记一下(不能再选)
就是这样:
del[tmp1] = del[tmp2] = del[u.pos] = true;
把链表的头指针(空)和尾指针(空)位置的答案设为inf,
因为当堆顶刚好是第一个点或者最后一个点时,
相邻的点不可能有两个(这里只找两边,不符合就放堆底,不会影响更新除了inf外的另一个),
就直接放在堆底不管就好啦。
QAQ为什么觉得越说越乱?还是看看代码吧。
(不想写了,所以很不要脸的贴上lxy大神的Codes,而且还很不要脸的改了码风)
Codes:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define LL long long
#define INF 1061109567
using namespace std;
const int MAXN = 1000000 + 50;
LL next[MAXN],pre[MAXN],A[MAXN],D[MAXN];
struct zt{
LL pos;
LL cost,v;
}l[MAXN];
bool operator < (zt a,zt b){
return a.v > b.v;
}
priority_queue <zt> q;
LL n,m,k,cnt;
int first;
LL tot,ans,now;
bool del[MAXN];
int main(){
scanf("%lld%lld",&n,&m);
scanf("%lld",&D[1]);
for(int i = 2;i <= n;i ++){
scanf("%lld",&D[i]);
A[++ cnt] = D[i] - D[i - 1];
}
for(int i = 1;i <= cnt;i ++){
pre[i] = i - 1;
next[i - 1] = i;
q.push((zt){i,1,A[i]});
}
pre[0] = -1;
next[cnt] = cnt + 1;
first = 0;
A[0] = INF;
A[cnt + 1] = INF;
cnt ++;
while(!q.empty()){
zt u = q.top();
q.pop();
if(del[u.pos])continue;
tot += u.cost;
now += u.v;
if(tot == m){
ans = now;
break;
}
u.v = -u.v;
u.v += A[pre[u.pos]] + A[next[u.pos]];
A[++ cnt] = u.v;
int tmp1 = pre[u.pos],tmp2 = next[u.pos];
next[pre[tmp1]] = cnt;
pre[next[tmp2]] = cnt;
pre[cnt] = pre[tmp1];
next[cnt] = next[tmp2];
del[tmp1] = del[tmp2] = del[u.pos] = true;
u.pos = cnt;
q.push(u);
}
printf("%lld",ans);
}
T2
DP
//QAQ状压是什么我不知道
Problem 2 :第二题(b.cpp)
题目描述
给定一个n+1个点(编号为0,1,2..n)的有向图,求从0号点出发,经过所有城市至少一次,且最后回到0号点的最短路。
输入格式
一行一个整数 n
接下来一个(n + 1)* (n + 1)的邻接矩阵表示城市两两之间的路径(a到b的路径长度不一定等于b到a的路径长度)
输出格式
一个整数表示最短路径。
样例输入
3
0 1 10 10
1 0 1 2
10 1 0 10
10 2 10 0
样例输出
8
数据范围及提示
1 <= n <= 15
A:这个题……搜索能过么?
B:好像可以记忆化。
A:那就dp?
END.
//再次惊叹小A小B分析问题的简洁……
首先,求最短路嘛,
很容易想到spfa,边跑最短路边记录边数。
可行。
具体怎么实现呢。
实际上多在队列里存一个变量就好啦~
不是要求所有点都走过么,
这样我们就需要知道哪些点走过了,哪些点没走过,
天哪,我们需要记录的是一个状态!
怎么记录?又快又好用的二进制!
n个点,我就用二进制存n个位置,起始都是0,
走过把0改成1,这样都走过的状态时什么呢?
当二进制数为(1 << n) - 1的时候。
这样最后输出的是dis[0][(1 << n) - 1]嘛。
复制一波代码,
Codes:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n,tot;
int dis[20][140000];
bool done[20][140000];
int first[20],nxt[1000];
struct edge{
int f,t,v;
}l[1000];
void build(int f,int t,int v){
l[++ tot] = (edge){f,t,v};
nxt[tot] = first[f];
first[f] = tot;
}
struct zt{
int num , ok;
};
queue<zt>q;
void spfa(){
q.push((zt){1,2});done[1][2] = 1,dis[1][2] = 0;
while(!q.empty()){
zt a = q.front(); q.pop();
int u = a.num,now = a.ok;done[u][now] = 0;
for(int i = first[u] ; ~i ; i = nxt[i]){
int v = l[i].t;
if(now&(1<<v)){
if(dis[v][now] > dis[u][now] + l[i].v){
dis[v][now] = dis[u][now] + l[i].v;
if(!done[v][now]){
q.push((zt){v,now});
done[v][now] = 1;
}
}
}
else {
if(dis[v][now+(1<<v)] > dis[u][now] + l[i].v){
dis[v][now+(1<<v)] = dis[u][now] + l[i].v;
if(!done[v][now+(1<<v)]){
q.push((zt){v,now+(1<<v)});
done[v][now+(1<<v)] = 1;
}
}
}
}
}
}
int main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
memset(dis,0x3f,sizeof(dis));
memset(first,-1,sizeof(first));
scanf("%d",&n);int x;
for(int i = 1 ; i <= n + 1 ; i ++)
for(int j = 1 ; j <= n + 1; j ++){
scanf("%d",&x);
if(i != j) build(i,j,x);
}
spfa();int end=0;
for(int i = 1 ; i <= n + 1; i ++)
end += (1<<i);
printf("%d",dis[1][end]);
return 0;
}
忘了忘了,还有一种解法,
就是小A小B说的那种,
dp!!
f[i][j]:状态为i,到j时的答案。
那方程就很好推啦~
f[i|(1<<k-1)][k]=min(f[i|(1<<k-1)][k],f[i][j]+dis[j][k]);
std Codes:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int INF=0x3f3f3f3f; int dis[20][20],f[1<<15][20]; int main() { freopen("b.in","r",stdin); freopen("b.out","w",stdout); int n; scanf("%d",&n); for(int i=0;i<=n;++i) for(int j=0;j<=n;++j) scanf("%d",&dis[i][j]); for(int k=0;k<=n;++k) for(int i=0;i<=n;++i) for(int j=0;j<=n;++j) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); memset(f,63,sizeof f); f[0][0]=0; int ful=(1<<n)-1; for(int i=0;i<=ful;++i) { for(int j=0;j<=n;++j) if(f[i][j]!=INF) { for(int k=1;k<=n;++k) f[i|(1<<k-1)][k]=min(f[i|(1<<k-1)][k],f[i][j]+dis[j][k]); } } int ans=INF; for(int i=1;i<=n;++i) ans=min(ans,f[ful][i]+dis[i][0]); printf("%d",ans); return 0; }
T3……不整理了。因为不会。
MAS:
这个时候我还能说要好好看题么!!!!
好好看题好好看题好好看题好好看题好好看题好好看题!!!