变换序列
(传送门)
题意
对于0…N-1共N个整数,给一个距离序列D0…DN-1,定义一个变换序列T0…TN-1使得每个i,Ti的环上距离等于Di。一个合法的变换序列应是0…N-1的一个排列,任务是要求出字典序最小的那个变换序列。
分析
一看就是二分图。左边为0~N-1的位置,右边为0~N-1的数字。如果第x个位置可以填y,则连一条线。题目所求为最大匹配。许多人用了稀奇古怪的方法来保证字典序,其实不用。不断寻找增广路,找到了就变,所以直接使用匈牙利就可以保证字典序最小。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+5;
int n;
int mat[maxn];
bool vis[maxn];
vector<int> edges[maxn];
bool dfs(int v)
{
for(int i=0;i<edges[v].size();i++)
{
int e=edges[v][i];
if(!vis[e])
{
vis[e]=1;
if(mat[e]==-1 || dfs(mat[e]))
{
mat[e]=v;
mat[v]=e;
return 1;
}
}
}
return 0;
}
int main()
{
cin>>n;
memset(mat,-1,sizeof(mat));
for(int i=1;i<=n;i++)
{
int a;
scanf("%d",&a);
int x=i-a,y=i+a;
if(x<=0) x+=n;
if(y>n) y-=n;
x+=n;
y+=n;
if(x>y) swap(x,y);
edges[i].push_back(x);
edges[i].push_back(y);
}
for(int i=n;i>=1;i--)
{
memset(vis,0,sizeof(vis));
if(!dfs(i))
{
cout<<"No Answer";
return 0;
}
}
for(int i=1;i<n;i++)
printf("%d ",mat[i]-n-1);
printf("%d\n",mat[n]-n-1);
return 0;
}
诗人小G
(传送门)
题意
有N个诗句需要被排版为若干行,顺序不能改变。一行内可以有若干个诗句,相邻诗句之间有一个空格。定义行标准长度L,每行的不协调度为|实际长度-L|P,整首诗的不协调度就是每行不协调度之和。任务是安排一种排版方案,使得整首诗的不协调度最小
分析
显然是动态规划问题,设前i个句子和为sum[i]
dp[i]表示以第i个为结尾的最小不协调度则有dp[i]=min(dp[j]+abs(sum[i]-sum[j]-l)^p);
写出DP方程有30分,加贪心50分,可证明状态转移具有决策单调性,可以加优化得到满分算法。如果在考场,不用费时费力证明,打个表就可以看出来。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+5;
const long long INF=1e18;
//long double 保存大于long long的数
long double dp[maxn];
long double sum[maxn];
int q[maxn],ll[maxn],rr[maxn],head,tail;
int n,m,limit,p;
long double POW(long double a)
{
if(a<0) a=-a;
long double ans=1;
for(int i=1;i<=p;i++) ans*=a;
return ans;
}
long double cal(int i,int j)
{
return dp[j]+POW(sum[i]-sum[j]+i-j-1-limit);
}
void update(int i)
{
while(ll[tail]>i && cal(ll[tail],q[tail])>cal(ll[tail],i)) rr[tail-1]=rr[tail--];
int l=ll[tail],r=rr[tail],pos=r+1;
while(l<=r)
{
int mid=(l+r)>>1;
if(cal(mid,q[tail])>cal(mid,i)) r=mid-1,pos=mid;
else l=mid+1;
}
if(pos<=rr[tail])
{
ll[tail+1]=pos;
rr[tail+1]=rr[tail];
rr[tail]=pos-1;
q[++tail]=i;
}
}
void solve()
{
head=tail=1;
ll[head]=1,rr[head]=n;
for(int i=1;i<=n;i++)
{
if(i>rr[head]) head++;
dp[i]=cal(i,q[head]);
update(i);
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
scanf("%d%d%d",&n,&limit,&p);
char s[32];
for(int i=1;i<=n;i++)
{
scanf("%s",s);
sum[i]=sum[i-1]+strlen(s);
}
solve();
if(dp[n]>INF) printf("Too hard to arrange\n");
else printf("%lld\n",(long long)dp[n]);
printf("--------------------\n");
}
return 0;
}
二叉查找树
(传送门)
题意
一棵Treap,每个节点有个互不相同的数据值和权值,一个访问频度。一个节点的访问代价为它的访问频度乘以它在树中的深度,整棵树的访问代价定义为所有节点的访问代价之和。节点的权值可以修改为任意实数,每修改一个节点的权值的代价为K。任务是修改一些节点的权值,使得整棵树的访问代价与修改代价之和最小。
分析
先把权值离散化,按数据值排序
sum[i]为前i个节点频度和
dp[i][j][w]表示把节点[i,j]合并成一颗根节点权值不小于w的子树所需的访问代价与修改代价的最小和,答案为dp[1][n][1]
dp[i][j][w]=min{ dp[i][k-1][w]+dp[k+1][j][w]+sum[j]-sum[i-1]+K,dp[i][k-1][a[k].weight]+dp[k+1][j][a[k].weight]+sum[j]-sum[i-1](a[k].weight>=w) } (i<=k<=j)
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=80;
const int INF=0x3f3f3f3f;
struct node
{
int key,weight,frequence;
} a[MAXN];
bool cmp_key(node aa,node bb) { return aa.key<bb.key; }
bool cmp_weight(node aa,node bb) { return aa.weight<bb.weight; };
int sum[MAXN];
int dp[MAXN][MAXN][MAXN];
int main()
{
int n,ewai;
cin>>n>>ewai;
for(int i=1;i<=n;i++)
scanf("%d",&a[i].key);
for(int i=1;i<=n;i++)
scanf("%d",&a[i].weight);
for(int i=1;i<=n;i++)
scanf("%d",&a[i].frequence);
sort(a+1,a+n+1,cmp_weight);
for(int i=1;i<=n;i++)
a[i].weight=i;
sort(a+1,a+n+1,cmp_key);
sum[0]=0;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i].frequence;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(a[i].weight>=j) dp[i][i][j]=a[i].frequence;
else dp[i][i][j]=a[i].frequence+ewai;
}
for(int w=n;w>=1;w--)
for(int t=1;t<n;t++)
for(int i=1;i+t<=n;i++)
{
int j=i+t,s=INF;
for(int k=i;k<=j;k++)
{
s=min(s,dp[i][k-1][w]+dp[k+1][j][w]+sum[j]-sum[i-1]+ewai);
if(a[k].weight>=w)
s=min(s,dp[i][k-1][a[k].weight]+dp[k+1][j][a[k].weight]+sum[j]-sum[i-1]);
}
dp[i][j][w]=s;
}
printf("%d\n",dp[1][n][1]);
return 0;
}
植物大战僵尸
(传送门)
题意
有一些植物,每个植物携带有一定的资源(可正可负),且有一个攻击范围,可以保护攻击位置上的另一个植物。有一群僵尸从右向左进攻植物,僵尸不能走到植物的攻击范围内。任务是求一个进攻方案,使得僵尸获得的资源尽可能多。
分析
建立图论模型,把植物当做点,携带的资源数目为顶点的权值。如果一个植物b在另一个植物a的攻击范围内,连有向边a-->b,表示a可以保护b。由于僵尸从右向左进攻,认为每个植物都被它右边相邻的植物保护,除最左边一列,对于每个植物a,向其右边植物连边b-->a表示b可以保护a。植物的攻击速度比僵尸快,所以当有的植物互相依赖(形成环),不可能被攻击到。在建图形成环的时候,只用最大流跑会把整个环都取到,所以,跑一半遍TopSort,把图中的环去掉。
代码
#include <bits/stdc++.h>
using namespace std;
#define S 0
#define T (m*n+1)
const int MAXN=1000+5;
const int INF=0x3f3f3f3f;
int n,m,total_cost;
int score[MAXN];
struct Edge
{
int v,w,next;
} edges[MAXN*500];
int head[MAXN],cnt,layer[MAXN];
void addEdge(int u,int v,int w)
{
edges[cnt]=(Edge){v,w,head[u]}; head[u]=cnt++;
edges[cnt]=(Edge){u,0,head[v]}; head[v]=cnt++;
}
bool vis[MAXN];
int out[MAXN];
void top_sort()
{
queue<int> Q;
while(!Q.empty()) Q.pop();
for(int i=1;i<=m*n;i++)
if(!out[i]) Q.push(i);
while(!Q.empty())
{
int x=Q.front(); Q.pop();
vis[x]=1;
if(score[x]>0)
total_cost+=score[x];
for(int i=head[x];i;i=edges[i].next)
if(!edges[i].w && !--out[edges[i].v])
Q.push(edges[i].v);
}
}
bool bfs()
{
memset(layer,-1,sizeof(layer));
layer[S]=0;
queue<int> Q;
while(!Q.empty()) Q.pop();
Q.push(S);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
for(int i=head[u];i!=-1;i=edges[i].next)
{
int v=edges[i].v;
if(layer[v]==-1 && edges[i].w)
{
layer[v]=layer[u]+1;
Q.push(v);
}
}
}
return (layer[T]>0);
}
int dfs(int x,int flow)
{
if(x==T) return flow;
int a,ans=0;
for(int i=head[x];i!=-1;i=edges[i].next)
{
int v=edges[i].v;
if(layer[v]==layer[x]+1 && edges[i].w && (a=dfs(v,min(flow,edges[i].w) ) ) )
{
edges[i].w-=a;
edges[i^1].w+=a;
ans+=a;
flow-=a;
if(!flow) break;
}
}
if(ans) return ans;
layer[x]=-1;
return 0;
}
int dinic()
{
int max_flow=0;
while(bfs())
while(int k=dfs(0,INF))
max_flow+=k;
return max_flow;
}
int main()
{
memset(head,-1,sizeof(head));
cin>>m>>n;
for(int i=1;i<=m*n;i++)
{
int num;
scanf("%d%d",&score[i],&num);
for(int j=1;j<=num;j++)
{
int x,y;
scanf("%d%d",&x,&y);
addEdge(x*n+y+1,i,INF);
out[x*n+y+1]++;
}
if(i%n)
{
addEdge(i,i+1,INF);
out[i]++;
}
}
top_sort();
for(int i=1;i<=m*n;i++)
if(vis[i])
{
if(score[i]>0) addEdge(S,i,score[i]);
else addEdge(i,T,-score[i]);
}
cout<<total_cost-dinic()<<endl;
return 0;
}
管道取珠
(传送门)
题意
上下两个管道,管道内排列着两种颜色的珠。按照某种次序取出珠,可以形成一个输出序列。不同的取珠方法可以形成相同的输出序列,设某种输出序列的取珠方法数为a[i],任务是求出所有输出序列的∑a[i]2。
分析
首先想到爆搜,只有30分,而且没有优化得到高分的头绪。见到有神犇用组合数学做了满分,膜拜。其实可以用DP来做
dp[i1][j1][i2][j2]可以表示为取珠方法X状态为(i1,j1),取珠方法Y状态为(i2,j2),X,Y的输出序列相同的有序对(X,Y)的数量。
由于X,Y同步,所以当i1,j1,i2都确定以后j2可以被表示为i1 + j1 - i2,状态表示可以减少一维。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=500+5;
const int MOD=1024523;
int n,m;
int dp[maxn][maxn][maxn];
char a[maxn],b[maxn];
inline void add(int &x,int y)
{
x+=y;
if(x>MOD)x-=MOD;
}
int main()
{
cin>>n>>m;
scanf("%s",a+1);scanf("%s",b+1);
reverse(a+1,a+n+1);
reverse(b+1,b+m+1);
dp[0][0][0]=1;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<=n;k++)
{
int l=i+j-k,t=dp[i][j][k];
if(l<0||l>m)continue;
if(a[i+1]==a[k+1])add(dp[i+1][j][k+1],t);
if(a[i+1]==b[l+1])add(dp[i+1][j][k],t);
if(b[j+1]==a[k+1])add(dp[i][j+1][k+1],t);
if(b[j+1]==b[l+1])add(dp[i][j+1][k],t);
}
printf("%d",dp[n][m][n]);
return 0;
}