最优卡组
一、题目
题目描述
chitanda
有
k
k
k 个卡包,第
i
i
i 个卡包里有
c
i
c_i
ci 张卡,每张卡有一个能力值,其中第
i
i
i 个卡包里的第
j
j
j 张卡具有
a
i
,
j
a_{i, j}
ai,j 点能力值。
他准备选择
k
k
k 张卡牌的组合,其中每个卡包要选择恰好一张卡牌。他希望这
k
k
k 张卡牌的能力值之和尽量大,请你告诉他在所有可能的组合里,能力值之和最大的
n
n
n 个组合分别具有多大的能力值。
输入格式
第一行包含两个整数
k
k
k 和
n
n
n,含义见题目描述。
接下来
k
k
k 行,每行描述一个卡包的信息,其中第
i
i
i 行的第一个整数表示
c
i
c_i
ci,接下来该行会有
c
i
c_i
ci 个整数,依次表示这个卡包里每张卡的能力值。
输出格式
输出一行,包含
n
n
n 个整数,相邻两个数字之间用空格隔开,其中第
i
i
i 个整数表示第
i
i
i 大的能力值之和。
二、解法
0x01 暴力解决
绑点的东西最恶心 ,就是搜索加一些奇技淫巧 也得不了分(
c
l
o
r
k
_
t
clork\_t
clork_t是个好东西 )。
我们换一种搜索思路,避免扩展出不必要的状态。
我们定义状态为在每个卡包的选取状态,如
1231
1231
1231就是在第1个卡包中选第1大的,在第2个卡包中选第2大的
…
…
\dots\dots
……发现每一种状态都是可以由其他状态转移过来,我们的搜索就是要让当前最优的状态去扩展它的后继,具体怎么实现能,我们维护一个优先队列,把最初状态塞进去,每次选最优的状态去扩展其他状态,把它们压入优先队列中(有点像
d
i
j
k
s
t
r
a
dijkstra
dijkstra),每次取出的状态我们就把它算进答案里面,直到取到
n
n
n个,时间复杂度
O
(
n
k
log
)
O(nk\log)
O(nklog)。
0x02 对暴力的优化
虽然我们上面的方法已经比无脑搜优秀很多了,但它还是免不了扩展出许多不必要的状态,我们尝试减少每个状态扩展出来的状态,就能减少时间复杂度,但是我们要考虑下列的两个原则:
1、所有状态必须能被不重复,不遗漏地扩展出来。
2、保证先扩展出来的状态一定由于后扩展出来的状态。
我们考虑一种很奇怪的贪心,先找到最后一位非1位(参照上文状态定义),我们就可以把这一位和下一位加1作为新的状态,如果该位是2的话,可以把它和后一位调换位置,这么就可以满足第一个原则,该算法时间复杂度为
O
(
3
n
log
)
O(3n\log)
O(3nlog),肯定过的了。
但是我们考虑条件二的时候,发现该算法不一定满足条件2,因为21(状态局部)不一定优于12,但12是由21扩展出来的,所以必须要满足21优于12,怎么解决这个问题呢?发现只要将卡包按最大值和最小值的差值从小到大排序即可(如果只有卡包里只有一个牌就不管),这样就保证了第二个原则。
0x03 后记及代码
其实作者讲的不是特别严谨,比如说该算法能满足第一个原则的证明,和想到这个算法的方法,都没有讲的很清楚,读者可以拿这位大佬的博客一起阅读。
日常贴代码qwq ?
#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
#define int long long
using namespace std;
const int MAXN = 100005;
int read()
{
int num=0,flag=1;
char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,m,sum;
vector<int> a[MAXN],ans;
struct node
{
int sum,pos,num;
bool operator < (const node &B) const {
return sum<B.sum;
}
};
bool cmp1(int a,int b)
{
return a>b;
}
bool cmp2(vector<int> a,vector<int> b)
{
return a[0]-a[1]<b[0]-b[1];
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
int k=read(),t=0,Max=0;
while(k--)
{
a[i].push_back(t=read());
Max=max(Max,t);
}
sum+=Max;
if(a[i].size()==1)
{
a[i].clear();
n--;i--;
continue;
}
sort(a[i].begin(),a[i].end(),cmp1);
}
sort(a+1,a+1+n,cmp2);
priority_queue<node> q;
m--;
q.push(node{sum-a[1][0]+a[1][1],1,2});
ans.push_back(sum);
while(m>0 && !q.empty())
{
node t=q.top();
q.pop();
ans.push_back(t.sum);
m--;
if(t.num<a[t.pos].size())
q.push(node{t.sum-a[t.pos][t.num-1]+a[t.pos][t.num],t.pos,t.num+1});
if(t.pos<n)
q.push(node{t.sum-a[t.pos+1][0]+a[t.pos+1][1],t.pos+1,2});
if(t.num==2 && t.pos<n)
q.push(node{t.sum+a[t.pos][0]-a[t.pos][1]-a[t.pos+1][0]+a[t.pos+1][1],t.pos+1,2});
}
for(int i=0;i<ans.size();i++)
printf("%lld ",ans[i]);
}
小奇探险
一、题目
题目描述
小奇去遗迹探险,遗迹里有
N
N
N 个宝箱,有的装满了珠宝,有的装着废品。
小奇有地图,所以它知道每一个宝箱的价值,但是它不喜欢走回头路,所以要按顺序拿这
N
N
N 个宝箱中的若干个。
拿宝箱很累的。一开始小奇的体力是
1
1
1,每得到一个宝箱之后,小奇得到的价值是体力
×
\times
× 宝箱的价值,之后它的体力就会变为原来的
k
k
k 倍
(
0
<
k
<
1
)
(0<k<1)
(0<k<1)。
小奇不喜欢连续放过很多宝箱,所以任意一段长度为
M
M
M 的序列中,小奇一定要取走其中的一个宝箱。
现在小奇想知道它能得到的最大价值和。
输入格式
第一行,两个整数
N
,
M
N,M
N,M,表示的含义如题目中所述;
第二行,一个小数
k
k
k,表示的含义如题目中所述,最多
4
4
4 位小数;
第三行,
N
N
N 个整数,第
i
i
i 个整数表示第
i
i
i 个宝箱的价值。
输出格式
输出一行,一个实数,表示小奇能得到的最大价值和,四舍五入保留两位小数。
数据范围
对于
30
%
30\%
30% 的数据,有
1
≤
N
≤
10
1\le N\le 10
1≤N≤10;
对于
60
%
60\%
60% 的数据,有
1
≤
N
≤
1000
1\le N\le 1000
1≤N≤1000;
对于
100
%
100\%
100% 的数据,有
1
≤
N
≤
100000
1\le N\le 100000
1≤N≤100000,
1
≤
M
≤
N
1\le M\le N
1≤M≤N,
0
<
k
<
1
0<k<1
0<k<1,
−
1
0
9
≤
-10^9\le
−109≤所有宝箱的价值
≤
1
0
9
\le 10^9
≤109
二、解法
一看这道题就是
d
p
dp
dp,考试时无脑码了一发,优化也只有80分。
我们先不考虑
k
k
k的限制,因为这道题没有限制取宝箱的个数,我们定义
d
p
[
i
]
dp[i]
dp[i]为在第i个点拿宝箱,在前面也拿宝箱的最大总价值,则:
d
p
[
i
]
=
max
{
d
p
[
j
]
+
a
[
i
]
}
∨
(
i
−
m
<
=
j
<
i
)
dp[i]=\max\{dp[j]+a[i]\}\vee (i-m<=j<i)
dp[i]=max{dp[j]+a[i]}∨(i−m<=j<i)
这个
d
p
dp
dp用滑窗来优化,可以做到
O
(
n
)
O(n)
O(n)。
我们考虑加入
k
k
k的限制,发现这样的话
d
p
dp
dp就要增加一维(然后当场去世),现在我们考虑怎么处理
k
k
k的问题,发现这多的一维是用来解决个数的记录,我们可以模仿
h
a
s
h
hash
hash的方式,从后往前
d
p
dp
dp,则:
d
p
[
i
]
=
d
p
[
j
]
×
k
+
a
[
i
]
∨
(
i
<
j
<
=
i
+
m
)
dp[i]=dp[j]\times k+a[i]\vee (i<j<=i+m)
dp[i]=dp[j]×k+a[i]∨(i<j<=i+m)
这样我们就可以避免个数的问题,时间复杂度还是
O
(
n
)
O(n)
O(n)。
??
#include <cstdio>
#include <cstring>
#include <iostream>
#define eps 1e-11
using namespace std;
const int MAXN = 100005;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*flag;
}
int n,m,head,tail,a[MAXN],q[MAXN];
double ans,k,dp[MAXN];
int main()
{
n=read();m=read();
scanf("%lf",&k);
for(int i=1;i<=n;i++)
a[i]=read();
q[0]=n+1;
for(int i=n;i>=1;i--)
{
while(head<=tail && q[head]-i>m) head++;
dp[i]=dp[q[head]]*k+a[i];
while(head<=tail && dp[q[tail]]<=dp[i]) tail--;
q[++tail]=i;
}
for(int i=1;i<=m;i++)
ans=max(ans,dp[i]);
printf("%.2lf",ans);
}
电压
一、题目
二、解法
我以前竟然做过类似的题,考试的时候还是蒙了。
考虑如果图是一颗树的话,那么条边都是可以选的,我们先建一颗生成树,再考虑向树上加入边,就会生成一个奇环和偶环,发现奇环上的边都是可选的,偶环上的边都是不可选的,这个可以用树上差分解决。
继续考虑两个非树边组合在一起的情况,我搞了一个图:
可以发现虽然两条数边生成了一个奇环,但这个奇环不会对答案造成影响(因为答案只会在第一个奇环中取),还有一些情况,读者可以手膜一下。
这样这道题就大概做出来了,还有几个特殊情况:
1、如果图不连通,在多个生成树中至多有1颗树有奇环。
2、如果某棵树只有一个奇环,那个生成奇环的非树边也可以作为一种答案。
??
#include <cstdio>
#include <vector>
using namespace std;
const int MAXN = 100005;
int read()
{
int num=0,flag=1;
char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,m,ans,all[MAXN],cnt,tot,p[MAXN],f[MAXN];
int c[MAXN],dep[MAXN],fa[MAXN][20];
struct edge
{
int v,next;
}e[2*MAXN];
struct node
{
int a,b;
}g[2*MAXN];
int findSet(int x)
{
if(x^p[x]) p[x]=findSet(p[x]);
return p[x];
}
void dfs(int u,int par)
{
fa[u][0]=par;
dep[u]=dep[par]+1;
for(int i=1;i<20;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=f[u];i;i=e[i].next)
if(e[i].v^par)
dfs(e[i].v,u);
}
int get(int u,int v)
{
for(int i=19;i>=0;i--)
{
if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(dep[fa[v][i]]>=dep[u]) v=fa[v][i];
}
if(u==v) return u;
for(int i=19;i>=0;i--)
{
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
}
return fa[u][0];
}
void cal(int u)
{
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa[u][0]) continue;
cal(v);
c[u]+=c[v];
}
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
int x=findSet(u),y=findSet(v);
if(x==y)
{
g[++cnt]=node{u,v};
continue;
}
p[x]=y;
e[++tot]=edge{u,f[v]},f[v]=tot;
e[++tot]=edge{v,f[u]},f[u]=tot;
}
for(int i=1;i<=n;i++)
if(i==findSet(i))
dfs(i,0);
for(int i=1;i<=cnt;i++)
{
int u=g[i].a,v=g[i].b;
int lca=get(u,v),t=dep[u]+dep[v]-2*dep[lca];
if(t%2==0)
{
c[u]++;c[v]++;
c[lca]-=2;
all[findSet(u)]++;
}
else
{
c[u]--;c[v]--;
c[lca]+=2;
}
}
int cot=0;
for(int i=1;i<=n;i++)
if(all[i])
cot++;
if(cot>1)
{
puts("0");
return 0;
}
for(int i=1;i<=n;i++)
if(i==findSet(i))
cal(i);
for(int i=1;i<=n;i++)
if(all[i]==1)
ans++;
for(int i=1;i<=n;i++)
if(i!=findSet(i) && (all[findSet(i)] || !cot))
ans+=(c[i]==all[findSet(i)]);
printf("%d\n",ans);
}