挖地雷
思路
起点必定是入度为的点。假设现在选了一个入度非的点,因为点权均为正值,所以选一个通过有向边连接的点作为起点更优。用拓扑排序,更新到达第个点能够获得的最大价值并记录路径。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=24,INF=0x3f3f3f3f;
int n,tot;
int head[N],a[N],pre[N],f[N],in[N];
queue<int> q;
struct Edge{
int nxt,to;
}e[N*N];
inline void add_edge(int u,int v,int flag)
{
e[++tot].nxt=head[u];
e[tot].to=v;
head[u]=tot;
if(flag)
add_edge(v,u,0);
}
void topo()
{
rep(i,1,n)
{
if(in[i]==0)
{
q.push(i);
f[i]=a[i];
}
}
while(!q.empty())
{
int u=q.front();q.pop();
bl(u,i)
{
int v=e[i].to;
if(f[v]<f[u]+a[v])
{
f[v]=f[u]+a[v];
pre[v]=u;
}
--in[v];
if(in[v]==0)
q.push(v);
}
}
}
void dfs(int x)
{
if(!pre[x])
{
cout<<x<<" ";
return;
}
dfs(pre[x]);
cout<<x<<" ";
}
int main()
{
cin>>n;
rep(i,1,n)
{
cin>>a[i];
}
rep(i,1,n-1)
{
rep(j,1,n-i)
{
int is;
cin>>is;
if(is)
{
add_edge(i,i+j,0);
++in[i+j];
}
}
}
topo();
int p=0;
rep(i,1,n)
{
if(f[i]>f[p])
{
p=i;
}
}
dfs(p);
cout<<endl;
cout<<f[p]<<endl;
}
五倍经验日
思路
先把输掉拿的经验维护进答案,现在开始嗑药,这个问题就变成一个典型的背包问题,容量为,第个物品的价值是,代价是。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=1004;
ll n,x,ans;
ll w[N],f[N],v[N];
int main()
{
scanf("%lld%lld",&n,&x);
rep(i,1,n)
{
ll x,y;
cin>>x>>y>>w[i];
ans+=x;
v[i]=y-x;
}
rep(i,1,n)
{
for(int j=x;j>=w[i];--j)
{
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
cout<<(ans+f[x])*5ll<<endl;
}
最长公共子序列
思路
通常求最长公共子序列复杂度为,对于这个题来说太慢了。注意到所给出的数组是全排列,也就是说、数组的长度和元素都相同,只有元素出现位置的差异。所以中的每一个元素都可以在中找到和自己相等的一个。把如果中有一个序列,他们中的每个元素在中的出现位置是单调递增的(也就是从左到右依次出现),那么这个序列必定是和的一个公共子序列。
考虑把中的元素换成该元素在中出现的位置,那就把问题转化成了求的最长上升子序列()。求解这个问题有的算法。扫一遍,设当前的是,大小为。如果,就直接把加到中。否则就通过二分查找(因为是有序的),找到中第一个大于的元素将其替换为。
正确性的证明:
第一种情况很显然是正确的,因为这样做会让当前的长度增加,得到一个更优的解。
第二种情况下,我们将当前中一个较大的数替换成一个较小的数,这样并不影响当前的长度,但是却可以让在之后更可能变长。记替换位置为,如果之后的数字在之后被替换掉了,那么当前的最后一个数字一定比原来小,就更可能接受之后的数字作为最大值,从而使答案更优。如果之后的数字没有被替换掉,的长度仍然正确,因此这样做就可能会让答案更优。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=1E5+10,INF=0x3f3f3f3f;
int n;
int b[N],p[N],q[N];
int main()
{
cin>>n;
rep(i,1,n)
{
int tmp;
scanf("%d",&tmp);
p[tmp]=i;
}
rep(i,1,n)
{
int tmp;
scanf("%d",&tmp);
b[i]=p[tmp];
}
int cnt=0;
rep(i,1,n)
{
if(q[cnt]<b[i])
q[++cnt]=b[i];
else
{
int pos=lower_bound(q+1,q+cnt+1,b[i])-q;
q[pos]=b[i];
}
}
cout<<cnt<<endl;;
}
关于路径的记录:参考博客
每次用更新队列,都记录一下它在队列中所处的位置,最后从出现位置为队列长度()的元素开始往前找,以它为结尾的倒数第二个元素就是出现位置为的元素。以此类推,就可以得到倒序的。
编辑距离
思路
设为的前个字符转化成的前个字符的编辑距离。状态间的转移有四种操作,分类讨论:
- 删除:
- 插入:
- 更改:
- 不变: 在其中取最小值维护即可。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=2005,INF=0x3f3f3f3f;
string a,b;
int f[N][N];
int main()
{
cin>>a>>b;
a=' '+a;
b=' '+b;
int n=a.size()-1,m=b.size()-1;
memset(f,INF,sizeof(f));
rep(i,0,m)
f[0][i]=i;
rep(i,0,n)
f[i][0]=i;
rep(i,1,n)
{
rep(j,1,m)
{
if(a[i]==b[j])
f[i][j]=min(f[i][j],f[i-1][j-1]);
else
{
f[i][j]=min(f[i][j],f[i][j-1]+1);//add
f[i][j]=min(f[i][j],f[i-1][j]+1);//del
f[i][j]=min(f[i][j],f[i-1][j-1]+1);//replace
}
}
}
cout<<f[n][m]<<endl;
}
大师
思路
设为以第个元素结尾,公差为的方案个数。那么前面的每一个元素都可以作为这个等差数列的倒数第二项,公差为,因为公差可为负数,因此加一个较大的常数(最大高度)防止出现负号下标。
记,则(第个元素可以与即成的所有等差数列合并,数目不变,另外还可以与第个形成一个新的等差数列,因此),每次更新时都用维护答案。因为单独一个元素也可作为等差数列,因此答案还要加。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=1002,H=2E4+2,M=998244353;
int n,ans;
int a[N],f[N][2*H];
int calc(int x)
{
int ret=(x-1)*(x-2)>>1;
return ret%M;
}
int main()
{
scanf("%d",&n);
rep(i,1,n)
scanf("%d",a+i);
ans=n;
rep(i,1,n)
{
rep(j,1,i-1)
{
f[i][a[i]-a[j]+H]+=f[j][a[i]-a[j]+H]+1;
f[i][a[i]-a[j]+H]%=M;
ans=(ans+f[j][a[i]-a[j]+H]+1)%M;
}
}
cout<<ans<<endl;
}
摆花
思路
设为前种花,放盆的方案数。枚举第种花放盆,则。
关于边界的初始化:只放第一种花,不管放多少盆(不超过)都只有一种方案,因此。放0盆花,不管是前几种都只有一种方案,因此。但其实这些状态都可以由直接或间接转移过去,因此初始化即可。
滚动数组优化:计算前种花时,只会用到前种的数据,因此可以滚动数组优化。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=104,M=1E6+7;
int n,m,ans;
int a[N],f[N];
int main()
{
scanf("%d%d",&n,&m);
rep(i,1,n)
scanf("%d",a+i);
f[0]=1;
rep(i,1,n)
{
for(int j=m;j>=0;--j)
{
rep(k,1,min(j,a[i]))
{
f[j]+=f[j-k];
f[j]%=M;
}
}
}
cout<<f[m]<<endl;
}