出题人:Chairman ,LXT ,qrc ,wyh
Problem 1:
题目来源:http://codevs.cn/problem/2169/
https://www.luogu.org/problem/show?pid=2376
https://www.luogu.org/problem/show?pid=2619
http://www.tyvj.cn/p/1032
考场上没有想到直接用除法计算次数…而是二分了…而且不是用最小面额的硬币使当前总和超过需求值…而是用了下一小的面额的硬币….
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,c,ls,x;
int bs[100010];
long long ans;
struct cons
{
int v,b;
}p[100010];
bool flag;
bool cmp(cons a,cons b)
{
return a.v>b.v;
}
int check(int k,int s)
{
if((p[k].v*s)>x)
return 0;
else return 1;
}
int find(int k)
{
int l=1;//存在次数为1的情况,边界不可以设为0
int r=p[k].b;
while(l+1<r)
{
int mid=(l+r)>>1;
if(check(k,mid))
l=mid;
else r=mid;
}
return l;
}
int main()
{
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++)
scanf("%d%d",&p[i].v,&p[i].b);
sort(p+1,p+n+1,cmp);//按面额从大到下排序
for(int i=n;i>=1;i--)
{
if(p[i].v>=c)//面额大于需求值的不用考虑,直接加进答案即可
{
ls=i+1;
break;
}
}
while(!flag)
{
x=c;
for(int i=ls;i<=n;++i)
if(p[i].v<=x&&p[i].b>0)//先用较大面额的硬币,尽量接近需求值
{
int t=min(x/p[i].v,p[i].b);//int t=min(find(i),p[i].b);
x-=t*p[i].v;
p[i].b-=t;
}
if(x>0)//用较小面额的硬币,满足>=c的条件,尽量减少浪费
{
for(int i=n;i>=ls;i--)
if(x<=p[i].v&&p[i].b>0)
{
p[i].b--;
x=0;
break;
}
}
if(x>0) break;
ans++;
}
for(int i=1;i<ls;i++)
ans+=p[i].b;
printf("%lld\n",ans);
return 0;
}
Problem 2:
题目来源:http://codevs.cn/problem/2451/
https://www.luogu.org/problem/show?pid=1896
http://www.lydsy.com/JudgeOnline/problem.php?id=1087
状压DP
用01串表示某一行放置的情况,直接枚举每一种状态需要2^9,考虑DP
dp[i][]j[h]表示第i行,从开始到现在已经放了j个国王,且第i行的状态(编号)是h的方案数。
则dp[i][j][h]=Σ(dp[i-1][j-cnt[h]][k]),k表示上一行的状态(编号),cnt表示h状态已经放置了的国王的数量。stay[i]储存i号状态的状态值,mp[i][j]表示i状态与j状态能否相临。
找出合法状态,删去不合法状态,预处理每一行的所有可行状态,枚举任意两种状态可否作为相邻两行进行摆放。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define RI register int
using namespace std;
typedef long long ll;
int n,ks,tot;
ll ans,pos;
ll dp[10][110][1010],stay[1010],cnt[1010];
bool mp[1010][1010];
void dfs(int k,int put,ll pos)//预处理出所有状态
{
stay[++tot]=pos;//存状态
cnt[tot]=k;//存放置棋子个数
if(k>=(n+1)/2||k>=ks)//边界条件
return;
for(RI i=put+2;i<=n;i++)//不可以相临放置故+2
dfs(k+1,i,pos+(1<<(i-1)));//状态改变
}
void init()
{
dfs(0,-1,0);//int i=put+2
for(RI i=1;i<=tot;i++)
for(RI j=1;j<=tot;j++)
mp[i][j]=mp[j][i]=((stay[i]&stay[j])||((stay[i]<<1)&stay[j])||((stay[i]>>1)&stay[j]))?0:1;
//枚举某两种状态是否能相邻,一共有三种不能的情况:上下都是1,左下、右上是1,左上、右下是1
for(RI i=1;i<=tot;i++)
dp[1][cnt[i]][i]=1ll;//边界,第三维用stay数组下标表示状态(最小表示法),相当于离散化
}
int main()
{
scanf("%d%d",&n,&ks);
init();
for(RI i=2;i<=n;i++)
for(RI j=0;j<=ks;j++)
for(RI h=1;h<=tot;h++)//枚举当前行状态
{
if(cnt[h]>j)
continue;
for(RI k=1;k<=tot;k++)//枚举上一行状态
{
if(mp[k][h]&&cnt[k]+cnt[h]<=j)
dp[i][j][h]+=dp[i-1][j-cnt[h]][k];//转移
}
}
for(RI i=1;i<=tot;i++)
ans+=dp[n][ks][i];
printf("%lld",ans);
return 0;
}
Problem 3:
题目来源:原创
考场写线段树只有20分….然而还写错了文件名…注意注意
正解是将函数分块处理,并对于每一块建立差分数组,处理出各个数字在各块中出现的次数及各块元素总和
询问时整块直接返回整块信息,其余部分树状数组查询
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define lowbit(x) ((x)&-(x))
using namespace std;
typedef unsigned long long LL;
int n;
int a[100005];
LL c[100005];
int L[100005],R[100005];
int Q;
int t[405][100005];
int belong[100005];
LL sum[405];
int B,NUM;
void add(int x,LL d)
{
while(x<=n)
{
c[x]+=d;
x+=lowbit(x);
}
}
LL ask(int x)
{
LL ret=0;
while(x)
{
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%d%d",&L[i],&R[i]);
}
B=sqrt(n)+1;
NUM=(n-1)/B+1;
for(int i=1;i<=n;i++)
belong[i]=(i-1)/B+1;//给函数分块~真是精妙
for(int i=1;i<=n;i++)
c[i]=a[i];
for(int i=1;i<=n;i++)//BIT储存元素
{
if(i+lowbit(i)<=n)
{
c[i+lowbit(i)]+=c[i];
}
}
for(int i=1;i<=n;i++)
{
t[belong[i]][L[i]]++;//差分,每当块内函数覆盖到一次元素,则该元素出现次数+1
t[belong[i]][R[i]+1]--; //分层处理块内函数对元素出现次数的影响
}
for(int i=1;i<=NUM;i++)
{
for(int j=1;j<=n;j++)
{
t[i][j]+=t[i][j-1];//差分数组求前缀和,统计块内函数对当前序列位置次数的影响
sum[i]+=t[i][j]*1ULL*a[j];//统计各块内函数影响总和(元素值*元素出现次数)
}
}
scanf("%d",&Q);
while(Q--)
{
int type,x,y;
scanf("%d%d%d",&type,&x,&y);
if(type==1)
{
for(int i=1;i<=NUM;i++)
{
sum[i]-=t[i][x]*1ULL*a[x];//修改每个块的总和
sum[i]+=t[i][x]*1ULL*y;
}
add(x,-a[x]);//修改BIT
a[x]=y;
add(x,a[x]);
}
else
{
int Ln,Rn;
Ln=belong[x],Rn=belong[y];
LL ans=0;
if(Ln==Rn)//若在同一块内
{
for(int i=x;i<=y;i++)
{
ans+=ask(R[i])-ask(L[i]-1);//BIT前缀和作差
}
}
else
{
for(int i=Ln+1;i<Rn;i++) ans+=sum[i];//加上整块总和
int lim;
lim=Ln*B;
lim=min(lim,y);
for(int i=x;i<=lim;i++)//加入单点
{
ans+=ask(R[i])-ask(L[i]-1);
}
lim=(Rn-1)*B+1;
lim=max(lim,x);
for(int i=lim;i<=y;i++)
{
ans+=ask(R[i])-ask(L[i]-1);
}
}
printf("%llu\n",ans);
}
}
return 0;
}
Problem 4:
题目来源:http://www.lydsy.com/JudgeOnline/problem.php?id=1017
题目内容恰如其名…某wyh自己做完了就出上了…
非常巧妙的树形DP..不会做..
只能扔代码了…
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define RI register int
using namespace std;
const int MAXN = 50 + 5;
const int MAXV = 2000 + 5;
const int INF = 1e9 + 7;
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
inline void read(int &x)
{
x = 0;
bool flag = 0;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-') flag = 1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
if(flag) x *= -1;
}
char s[5];
int n,m,x,t,q,cnt,tot,ans;
int p[MAXN],lmt[MAXN],c[MAXN];
int f[MAXN][105][MAXV],g[MAXN][MAXV],h[MAXN][MAXV];
int first[MAXN],next[MAXN << 1],ru[MAXN],chu[MAXN];
//f[i][j][k] 第i种物品,j件用来合成,花钱k,最大价值
//g[i][j] 以某点x为根,前i棵子树,花钱j,最大价值
struct edge
{
int f,t,v;
}l[MAXN << 1];
inline void build(int ff,int t,int v)
{
l[++ cnt] = (edge){ff,t,v};
next[cnt] = first[ff];
first[ff] = cnt;
ru[t] ++,chu[ff] ++;
}
void init()
{
cnt = 0;
memset(first,-1,sizeof(first));
memset(f,-0x3f3f3f3f,sizeof(f));
}
void solve(int x)
{
if(!chu[x])
{
//更新叶子节点(低级)
lmt[x] = min(lmt[x],m / c[x]);
for(int i = 0;i <= lmt[x];i ++)
for(int j = i;j <= lmt[x];j ++)
f[x][i][j * c[x]] = p[x] * (j - i);
return;
}
//更新花费和数量,转换为背包问题
lmt[x] = INF,c[x] = 0;
for(int i = first[x];i != -1;i = next[i])
{
int v = l[i].t;solve(v);
c[x] += c[v] * l[i].v;
lmt[x] = min(lmt[x],lmt[v] / l[i].v);
}
lmt[x] = min(lmt[x],m / c[x]);
memset(g,-0x3f3f3f3f,sizeof(g));
g[0][0] = 0;
for(int t = lmt[x];t >= 0;t --)
{
int tot = 0;
for(int i = first[x];i != -1;i = next[i])
{
tot ++;
int v = l[i].t;
for(int j = 0;j <= m;j ++)
for(int k = 0;k <= j;k ++)
g[tot][j] = max(g[tot][j],g[tot - 1][j - k] + f[v][t * l[i].v][k]);
}
for(int j = 0;j <= t;j ++)
for(int k = 0;k <= m;k ++)
f[x][j][k] = max(f[x][j][k],g[tot][k] + p[x] * (t - j));
}
}
int main()
{
freopen("fangak.in","r",stdin);
freopen("fangak.out","w",stdout);
init();
read(n),read(m);
for(RI i = 1;i <= n;i ++)
{
read(p[i]);
scanf("%s",s +1);
if(s[1] == 'A')
{
read(x);
while(x --)
{
read(t),read(q);
build(i,t,q);
}
}
else read(c[i]),read(lmt[i]);
}
int tot = 0,ans = -INF;
for(int x = 1;x <= n;x ++)
if(!ru[x])
{
solve(x);tot ++;
for(int i = 0;i <= m;i ++)
for(int j = 0;j <= i;j ++)
for(int k = 0;k <= lmt[x];k ++)
h[tot][i] = max(h[tot][i],h[tot - 1][j] + f[x][k][i - j]);
}
for(int i = 0;i <= m;i ++)
ans = max(ans,h[tot][i]);
printf("%d\n",ans);
return 0;
}