A - Jury Compromise
题意:
有n(<=200)个人作为陪审团的被选举成员,分别有正方和反方给予的评价,要求在这n个人中选出m(<=20)个人,在保证正方评价之和与反方评价之和差最小的情况下,使得评价分数总和最高。
题解:
0.定义状态fi][k]当前已经选取了i个人并且这i个人的差值为k时的评分总和的最大值,p[i][k]当前已经选取了i个人并且这i个人的差值为k时第i个人的编号。
1.如果令c[i]表示第i个人正方与反方的差,s[i]表示第i个人正方与反方的和,那么状态转移f[i][k+c[j]] = f[i-1][k]+s[j],p[i][k+c[j]] = j 条件:f[i][k+c[j]] < f[i-1][c[j]]+s[j]&&当前选的这第i个人(j)没有被选过.边界为dp[0][0] = 0;
2.但是下标的差值有可能为负数,那么下标总体往右平移就好了,最后再找出差值最小的dp状态,那么正方的总和为(dp[m][c]+c)/2,反方即为(dp[m][c]-c)/2
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e3+7;
int kase = 0,n,m,c[N],a[N],b[N],s[N],d[N][N],p[N][N],ret[N];
bool select(int i,int k,int x,int c[]){
while(i && p[i][k] != x)k -= c[p[i][k]],--i;
return i;
}
void print(int i,int k,int c[]){
while(i)ret[i] = p[i][k],k -= c[p[i][k]],--i;
sort(ret+1,ret+1+m);
for(int i = 1;i <= m;++i)printf("%d ",ret[i]);
cout<<endl<<endl;
}
int main(){
while(scanf("%d%d",&n,&m) != EOF && n && m){
memset(d,-1,sizeof(d));
memset(p,-1,sizeof(p));
for(int i = 1;i <= n;++i){
scanf("%d%d",&a[i],&b[i]);
c[i] = a[i]-b[i],s[i] = a[i]+b[i];
}
int M = 20*m;
d[0][M] = 0;
for(int i = 1;i <= m;++i){
for(int k = 0;k <= 2*M;++k)if(d[i-1][k]>=0){
for(int j = 1;j <= n;++j)if(d[i][k+c[j]]<d[i-1][k]+s[j] && !select(i-1,k,j,c)){
p[i][k+c[j]] = j;
d[i][k+c[j]] = d[i-1][k]+s[j];
}
}
}
int x;
for(x = 0;x <= M;++x)if(d[m][M-x] >= 0 || d[m][M+x] >= 0)break;
int minc = d[m][M-x]>d[m][M+x]?M-x:M+x;
printf("Jury #%d\n",++kase);
printf("Best jury has value %d for prosecution and value %d for defence:\n",(d[m][minc]+minc-M)>>1,(d[m][minc]-minc+M)>>1);
print(m,minc,c);
}
}
B - Number Game
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e1+7;
const int AS = (1<<21)-1;
int dp[AS<<1],S,n,kase,ret[N],cnt,x[N];
int update(int S,int x){
S |= 1<<x;
for(int i = 2;i+x <= 20;++i)if(S&(1<<i))S |= 1<<i+x;
return S;
}
int search(int S){
if(dp[S] != -1)return dp[S];
for(int i = 2;i <= 20;++i)if(!(S&(1<<i))){
int S0 = update(S,i);
if(!search(S0))return dp[S] = 1;
}
return dp[S] = 0;
}
int main(){
while(scanf("%d",&n) && n){
memset(dp,-1,sizeof(dp));
S = AS,dp[AS] = 0,cnt = 0;
for(int i = 0;i < n;++i)scanf("%d",&x[i]),S ^= 1<<x[i];
for(int i = 0;i < n;++i){
int S0 = update(S,x[i]);
if(!search(S0))ret[++cnt] = x[i];
}
printf("Test Case #%d\n",++kase);
cnt ? printf("The winning moves are:") : printf("There's no winning move.");
for(int i = 1;i <= cnt;++i)printf(" %d",ret[i]);
cout<<endl<<endl;
}
return 0;
}
C - Prison rearrangement
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 420;
const int M = 40010;
#define clr(a,b) memset(a,b,sizeof(a))
int n,m,kase;
int head[N],ecnt;
struct edge{int v,nxt;}e[M<<1];
void adde(int u,int v){e[ecnt] = (edge){v,head[u]};head[u] = ecnt++;}
int vis[N],T;
int dp[N][N],S0[N],S1[N];
void pre(){
T = 0,clr(vis,0),clr(S0,0),clr(S1,0);
ecnt = 0,clr(head,-1),clr(e,0),clr(dp,0);
}
void dfs(int u){
vis[u] = 1;
u <= n ? S0[T] += 1 : S1[T] += 1;
for(int it = head[u];it != -1;it = e[it].nxt)if(!vis[e[it].v])dfs(e[it].v);
}
int main(){
scanf("%d",&kase);
while(kase--){
pre();
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;++i){
int u,v;
scanf("%d%d",&u,&v);v += n;
adde(u,v),adde(v,u);
}
for(int i = 1;i <= n<<1;++i)if(!vis[i])++T,dfs(i);
dp[0][0] = 1;
for(int k = 1;k <= T;++k){
for(int i = n>>1;i >= S0[k];--i){
for(int j = n>>1;j >= S1[k];--j){
dp[i][j] = dp[i][j] || dp[i-S0[k]][j-S1[k]];
}
}
}
for(int i = n>>1;i >= 0;--i)if(dp[i][i]){
printf("%d\n",i);break;
}
}
return 0;
}
D - Clans on the Three Gorges
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 110;
const int inf = 1e8;
int n,m,k,A[N],B[N],C[N],kase;
int dN[N][N],dM[N][N],dK[N][N];
#define mini(a,b,c) min(min(a,b),c)
int main(){
scanf("%d",&kase);
while(kase--){
scanf("%d%d%d",&n,&m,&k);
for(int i = 1;i <= n;++i)scanf("%d",&A[i]);
for(int i = 1;i <= m;++i)scanf("%d",&B[i]);
for(int i = 1;i <= k;++i)scanf("%d",&C[i]);
for(int i = 0;i < N;++i)
for(int j = 0;j < N;++j)
dN[i][j] = dM[i][j] = dK[i][j] = inf;
dN[n+1][0] = dM[m+1][0] = dK[k+1][0] = 0;
for(int i = n;i >= 1;--i)
for(int j = 1;j <= m;++j)
dN[i][j] = mini(dN[i+1][j-1],dN[i+1][j],dN[i][j-1])+fabs(A[i]-B[j]);
for(int i = m;i >= 1;--i)
for(int j = 1;j <= k;++j)
dM[i][j] = mini(dM[i+1][j-1],dM[i+1][j],dM[i][j-1])+fabs(B[i]-C[j]);
for(int i = k;i >= 1;--i)
for(int j = 1;j <= n;++j)
dK[i][j] = mini(dK[i+1][j-1],dK[i+1][j],dK[i][j-1])+fabs(C[i]-A[j]);
int ret = inf;
for(int i = 0;i <= n+1;++i)
for(int j = 0;j <= m+1;++j)
for(int l = 0;l <= k+1;++l){
ret = min(ret,dN[i][j]+dM[j][l]+dK[l][i]);
ret = min(ret,dN[i+1][j]+dM[j][l]+dK[l][i]);
ret = min(ret,dN[i][j]+dM[j+1][l]+dK[l][i]);
ret = min(ret,dN[i][j]+dM[j][l]+dK[l+1][i]);
ret = min(ret,dN[i+1][j]+dM[j+1][l]+dK[l][i]);
ret = min(ret,dN[i][j]+dM[j+1][l]+dK[l+1][i]);
ret = min(ret,dN[i+1][j]+dM[j][l]+dK[l+1][i]);
ret = min(ret,dN[i+1][j]+dM[j+1][l]+dK[l+1][i]);
}
cout<<ret<<endl;
}
return 0;
}
E - Crossed Matchings
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e3+7;
int kase,n,m,dp[N][N],A[N],B[N];
int main(){
scanf("%d",&kase);
while(kase--){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i)scanf("%d",&A[i]);
for(int i = 1;i <= m;++i)scanf("%d",&B[i]);
memset(dp,0,sizeof(dp));
for(int i = 2;i <= n;++i){
for(int j = 2;j <= m;++j){
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
if(A[i] == B[j])continue;
int S0 = 0,S1 = 0;
for(S0 = i-1;S0 >= 1;--S0)if(A[S0] == B[j])break;
for(S1 = j-1;S1 >= 1;--S1)if(A[i] == B[S1])break;
if(S0&&S1)dp[i][j] = max(dp[i][j],dp[S0-1][S1-1]+2);
}
}
cout<<dp[n][m]<<endl;
}
return 0;
}
F - SUBTRACT
定义一种操作,操作con{i}就是将a[i]-a[i+1]取出进行合并,再加入到a[i]的位置,a[i+1]消失,进行n-1次操作后,会剩下一个数字.给定长度为n(<=100)的数列及目标t(最后剩下的数字),求操作顺序(Special Judge).
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e2+7;
const int M = 1e4+7;
#define clr(a,b) memset(a,b,sizeof(a))
int n,t,d[M],p[N][M],flag[N],ret[N],x[N];
int main(){
while(scanf("%d%d",&n,&t) != EOF){
int sum = 0,cnt = 0;
clr(d,0),clr(flag,0),clr(p,0);
for(int i = 1;i <= n;++i)scanf("%d",&x[i]),sum += x[i],x[i]<<=1;
t = t+sum-x[1];
for(int i = 3;i <= n;++i){
for(int j = t;j >= x[i];--j){
if(d[j] > d[j-x[i]]+x[i])p[i][j] = 0;
else d[j]=d[j-x[i]]+x[i],p[i][j] = 1;
}
}
int v = t;
for(int i = n;i >= 3;--i)if(p[i][v])flag[i] = 1,v -= x[i];
for(int i = 3;i <= n;++i)if(flag[i])ret[++cnt] = i-1;
for(int i = 0;i < cnt;++i)ret[i+1] -= i;
for(int i = 1;i <= cnt;++i)printf("%d\n",ret[i]);
for(int i = cnt+1;i < n;++i)puts("1");
}
return 0;
}
G - Special Experiment
有n(<=200)个原子和m(<=200)个光子,选出最大能量的原子集合,但是如果有任意两个原子的能力差等于一个光子的能量,那么这种方案是不行的。
题解:
0.如果两个原子的能量的差等于一个光子的能量,那么他们就面对选或是不选的情况,题目需要求最大能量的原子集合,那么是明显的树形dp(类似于"没有上司的舞会")
1.定义状态:dp[u][0/1]表示不选/选u这个节点的最大能量
2.状态转移:dp[u][0] += {max(dp[v][0],dp[v][1])}
dp[u][1] += {dp[v][0]}+w[u]
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e3+47;
int n,m,x,w[N],flag[1000000],ecnt,head[N],vis[N],dp[N][2];
struct edge{int v,nxt;}e[N*N];
#define clr(a,b) memset(a,b,sizeof(a))
void adde(int u,int v){e[ecnt].v = v,e[ecnt].nxt = head[u],head[u] = ecnt++;}
void search(int u){
vis[u] = 1;
for(int it = head[u];it != -1;it = e[it].nxt)if(!vis[e[it].v]){
int v = e[it].v;
search(v);
dp[u][1] += dp[v][0];
dp[u][0] += max(dp[v][0],dp[v][1]);
}
dp[u][1] += w[u];
}
int main(){
while(scanf("%d%d",&n,&m) != EOF && n+m){
ecnt = 0;
clr(vis,0),clr(dp,0),clr(head,-1),clr(flag,0);
for(int i = 1;i <= n;++i)scanf("%d",&w[i]);
for(int i = 1;i <= m;++i)scanf("%d",&x),flag[x] = 1;
for(int i = 1;i <= n;++i)
for(int j = 1;j <= n;++j)
if(flag[abs(w[i]-w[j])])adde(i,j);
int ret = 0;
for(int i = 1;i <= n;++i)if(!vis[i])search(i),ret += max(dp[i][0],dp[i][1]);
cout<<ret<<endl;
}
return 0;
}
H - Chores
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5+7;
int maxi,w,m,x,dp[N],n,ret;
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;++i){
maxi = 0;
scanf("%d%d",&w,&m);
while(m--)scanf("%d",&x),maxi = max(maxi,dp[x]);
dp[i] += maxi+w;
}
for(int i = 1;i <= n;++i)ret = max(ret,dp[i]);
cout<<ret<<endl;
return 0;
}
I - Moo University - Financial Aid
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 4e5+7;
priority_queue <int> pq;
int n,m,f,S,sum,f[N],g[N];
struct node{
int v,w;
bool operator < (const node &rhs)const{return v > rhs.v;}
}x[N];
int main(){
while(scanf("%d%d%d",&m,&n,&f) != EOF){
for(int i = 1;i <= n;++i)scanf("%d%d",&x[i].v,&x[i].w);
sort(x+1,x+1+n);
while(!pq.empty())pq.pop();
sum = 0,S = m-1>>1;
for(int i = 1;i <= S;++i)pq.push(x[i].w),sum += x[i].w;f[S] = sum;
for(int i = S+1;i <= n;++i){
if(x[i].w < pq.top())sum = sum-pq.top()+x[i].w,pq.pop(),pq.push(x[i].w);
f[i] = sum;
}
while(!pq.empty())pq.pop();
sum = 0,S = m-1>>1;
for(int i = n;i >= n-S+1;--i)pq.push(x[i].w),sum += x[i].w;g[n-S+1] = sum;
for(int i = n-S;i >= S;--i){
if(x[i].w < pq.top())sum = sum-pq.top()+x[i].w,pq.pop(),pq.push(x[i].w);
g[i] = sum;
}
bool flag = false;
for(int i = S+1;i <= n-S;++i)if(f[i-1]+g[i+1]+x[i].w <= f){
flag = true;printf("%d\n",x[i].v);break;
}
flag ? 1 : puts("-1");
}
return 0;
}
J - Folding
0.很明显的区间dp,定义状态dp[l][r].L表示在区间[l,r]的最小长度,dp[l][r].S表示区间[l,r]最小长度的字串形式。
1.状态转移:
①:dp[l][r].L = 数字位数+2(两个括号)+k
dp[l][r].S = "数字(字符串)" 条件:(如果可以折叠,k表示当前字串的重复长度)
②:dp[l][r].L = dp[l][k].L+dp[k+1][r].L
dp[l][r].S = dp[l][k].S+dp[k+1][r].S 条件(dp[l][r].L > dp[l][k].L+dp[k+1][r].L)
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int inf = 1e9+7;
const int N = 105;
struct dpregister{int l;char str[105];}dp[N][N];
int scnt = 0;
char str[N],ch[N];
int main(){
scanf("%s",str);
int n = strlen(str);
for(int i = 0;i < n;++i)dp[i][i].l = 1,dp[i][i].str[0] = str[i],dp[i][i].str[1] = '\0';
for(int len = 2;len <= n;++len){
for(int l = 0;l+len-1 <= n;++l){
int r = l+len-1;
dp[l][r].l = inf;
for(int k = 1;k < len;++k)if(len%k == 0){
int q = l+k,p = l;
while(q <= r){
if(str[q] != str[p])break;
p++,q++;
if(p == l+k)p = l;
}
if(q > r){
int cnt = len/k,scnt = -1;
while(cnt){ch[++scnt] = cnt%10+'0',cnt /= 10;}
for(int p = 0;p<<1 <= scnt;++p)swap(ch[p],ch[scnt-p]);
strcpy(dp[l][r].str,ch);
dp[l][r].str[++scnt] = '(';
for(int p = 0;dp[l][l+k-1].str[p];++p)dp[l][r].str[++scnt] = dp[l][l+k-1].str[p];
dp[l][r].str[++scnt] = ')';
dp[l][r].l = scnt+1;
dp[l][r].str[++scnt] = '\0';
break;
}
}
for(int k = l;k < r;++k)if(dp[l][r].l > dp[l][k].l+dp[k+1][r].l){
dp[l][r].l = dp[l][k].l+dp[k+1][r].l;
strcpy(dp[l][r].str,dp[l][k].str);
strcat(dp[l][r].str,dp[k+1][r].str);
}
}
}
printf("%s\n",dp[0][n-1].str);
return 0;
}
K - Naptime
一天有n(<=3830)个小时,有一只奶牛要睡m个小时,如果从a睡到b那么获得的权值为w[a+1]+w[a+2]+...w[b]并且它可以睡多次但是总的时间不能超过m,但是这一天可能是个环,存在[b,1,a]的情况,求最大获得的权值。
题解:
0.定义状态:dp[i][j][0/1]表示前i个小时,睡了j个小时,当前奶牛是醒/睡着的。
1.因为出现环的本质是因为第1个小时睡并且第n个小时也睡,那么有以下三种情况
状态转移:dp[i][j][0] = max(dp[i-1][j][1],dp[i-1][j][0])
dp[i][j][1] = max(dp[i-1][j-1][1]+w[i],dp[i-1][j-1][0])
①第1个小时不睡,那么不会出现环的情况.
边界dp[1][0][0] = 0,dp[1][0][1] = -inf 答案为max{dp[n][m][0],dp[n][m][1])
②第1个小时睡,第n个小时不睡,那么不会出现环的情况
边界dp[1][0][0] = -inf,dp[1][1][1] = 0 答案为dp[n][m][0]
③第1个小时睡,第n个小时也睡,那么最优的情况必定为环
边界dp[1][0][0] = -inf,dp[1][1][1] = w[1],答案为dp[n][m][1]
2.但是3830*3830*2会MLE,所以要滚动起来~~~
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
const int inf = 1e9+7;
int dp[2][N][2],w[N],ret,n,m,c;
#define clr(a,b) memset(a,b,sizeof(a))
void update(){
c = 1;
for(int i = 2;i <= n;++i){
c ^= 1,dp[c][0][0] = 0;
for(int j = 1;j <= m;j++){
dp[c][j][0] = max(dp[c^1][j][0],dp[c^1][j][1]);
dp[c][j][1] = max(dp[c^1][j-1][1]+w[i],dp[c^1][j-1][0]);
}
}
}
int main(){
while(scanf("%d%d",&n,&m) != EOF){
ret = 0;
for(int i = 1;i <= n;++i)scanf("%d",&w[i]);
clr(dp,128),dp[1][1][1] = 0,dp[1][0][0] = -inf;
update(),ret = max(ret,max(dp[c][m][0],dp[c][m][1]));
clr(dp,128),dp[1][0][0] = -inf,dp[1][1][1] = 0;
update(),ret = max(ret,dp[c][m][0]);
clr(dp,128),dp[1][0][0] = -inf,dp[1][1][1] = w[1];
update(),ret = max(ret,dp[c][m][1]);
cout<<ret<<endl;
}
return 0;
}
L - Greatest Common Increasing Subsequence
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2e3+7;
int T,dp[N][N],A[N],B[N],n,m;
int main(){
scanf("%d",&T);
while(T--){
memset(dp,0,sizeof(dp));
scanf("%d",&n);
for(int i = 1;i <= n;++i)scanf("%d",&A[i]);
scanf("%d",&m);
for(int i = 1;i <= m;++i)scanf("%d",&B[i]);
for(int i = 1;i <= n;++i){
int maxi = 0,id;
for(int j = 1;j <= m;++j){
if(A[i] == B[j])dp[i][j] = maxi+1;
if(A[i] != B[j]){
if(A[i]>B[j])maxi = max(maxi,dp[i-1][j]);
dp[i][j] = dp[i-1][j];
}
}
}
int ret = 0;
for(int i = 1;i <= m;++i)ret = max(ret,dp[n][i]);
cout<<ret<<endl;
if(T)cout<<endl;
}
return 0;
}