[JSOI2010]旅行
挺神的一道 (dp) 题。枚举 (t),代表选择排序后前 (t) 条边在从 (1) 到 (n) 新的路径上,设 (f_{i,j,k}) 表示到点 (i),经过前 (t) 条中的 (j) 条边,使用了 (k) 次交换边权的最短路,用最短路的形式进行 (dp)。转移有:
(f_{to,j+1,k}=f_{u,j,k}+w_{j+1}),前 (t) 条边包括当前边。
(f_{to,j+1,k+1}=f_{u,j,k}+w_{j+1}),前 (t) 条边不包括当前边并使用一次交换。
(f_{to,j,k}=f_{u,j,k}+w),前 (t) 条边不包括当前边,不使用交换(此处 (w) 指当前边的边权)。
时间复杂度大概是(O(nm^2k)),反正可以过。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
#define inf (2100000000)
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
inline int min(int x,int y){return x<y?x:y;}
const int N=52,S=152;
int n,m,s;
struct Edge{
int u,v,w;
}e[S];
bool comp(const Edge &x,const Edge &y){
return x.w<y.w;
}
struct node{
int i,j,k;
node(int u=0,int e=0,int p=0){i=u,j=e,k=p;}
};
std::vector<int> p[N];
int vis[N][S][N],f[N][S][N];
int ans=inf;
inline void bfs(int t){
std::queue<node> q;
memset(vis,0,sizeof vis),memset(f,0x3f,sizeof f);
f[1][0][0]=0,q.push(node(1,0,0));
while(!q.empty()){
node now=q.front();q.pop();
int u=now.i,j=now.j,k=now.k;vis[u][j][k]=0;
for(int i=0,sz=p[u].size();i<sz;i++){
int id=p[u][i],v=e[id].u;
if(v==u)v=e[id].v;
if(id<=t){
if(j<t&&f[u][j][k]+e[j+1].w<f[v][j+1][k]){
f[v][j+1][k]=f[u][j][k]+e[j+1].w;
if(!vis[v][j+1][k])vis[v][j+1][k]=1,q.push(node(v,j+1,k));
}
}
else{
if(j<t&&k<s&&f[u][j][k]+e[j+1].w<f[v][j+1][k+1]){
f[v][j+1][k+1]=f[u][j][k]+e[j+1].w;
if(!vis[v][j+1][k+1])vis[v][j+1][k+1],q.push(node(v,j+1,k+1));
}
if(f[u][j][k]+e[id].w<f[v][j][k]){
f[v][j][k]=f[u][j][k]+e[id].w;
if(!vis[v][j][k])vis[v][j][k]=1,q.push(node(v,j,k));
}
}
}
}
}
int main(){
n=rd(),m=rd(),s=rd();
for(int i=1;i<=m;i++)e[i].u=rd(),e[i].v=rd(),e[i].w=rd();
std::sort(e+1,e+m+1,comp);
for(int i=1;i<=m;i++)
p[e[i].u].push_back(i),p[e[i].v].push_back(i);
for(int i=0;i<=m;i++){
bfs(i);
for(int j=0;j<=s;j++)ans=min(ans,f[n][i][j]);
}
printf("%d
",ans);
return 0;
}
[JSOI2010]找零钱的洁癖
正解不知道。
由于种类数较少可以先暴力 (bfs),如果快爆了还没有搜到答案就及时结束,用贪心算出一个答案。然后就过了。
求正解qwq
#include <cstdio>
#include <map>
#define inf (210000000000ll)
typedef long long ll;
inline ll rd(){
ll x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
inline ll min(ll x,ll y){return x<y?x:y;}
const int N=1000002;
std::map<ll,int> mp;
int n;
ll x,a[N];
ll v[N],s[N];
int hd=0,tl=1;
inline ll bfs(){
while(hd<tl){
hd++;
for(int i=1;i<=n;i++){
tl++;
if(tl==N-2)return inf;
s[tl]=s[hd]+1;
if(v[hd]<x)v[tl]=v[hd]+a[i];
else v[tl]=v[hd]-a[i];
if(v[tl]==x)return s[tl];
if(mp[v[tl]])tl--;
else mp[v[tl]]=1;
}
}
return inf;
}
inline ll get(ll x){
ll ans=0;
for(int i=n;i>=1;i--){
if(!a[i])continue;
ans+=x/a[i];
x-=x/a[i]*a[i];
}
if(x)return inf;
return ans;
}
int main(){
x=rd();
int i=1;ll k;
while(~scanf("%lld",&k))a[i++]=k;
n=i-1;
printf("%lld
",min(bfs(),get(x)));
return 0;
}
[JSOI2010]挖宝藏
对于一个宝藏,如果要挖到它,则必须挖掉它上面的一个倒三角形,这个三角形会对应 (x) 轴的一段区间,这是可以直接算出来的。所以我们把每一个宝藏存在一个结构体里,包含这个区间、它本身的价值、挖到它至少得到的价值(即把上面的三角形中所有的宝藏的价值加起来)和挖它的代价。
考虑 (dp)。设 (f_i) 表示挖前 (i) 个宝藏,第 (i) 个必须挖能得到的最大价值。
先将宝藏按右端点从小到大排序,枚举 (1le j<i),如果 (j) 宝藏的区间与 (i) 的区间无交集,则可以直接取 (f_j) 进行转移。
若 (i) 和 (j) 有交,则维护一个指针,暴力处理重复的部分。由于宝藏的右端点是单调的,所以指针只会右移,复杂度仍然是 (O(n))。
总复杂度是 (O(n^2)),足以通过本题。
#include <cstdio>
#include <algorithm>
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
inline int max(int x,int y){return x>y?x:y;}
const int N=1002;
int n;
struct node{
int l,r,p1,p2,c;
bool operator < (const node &y)const{
return (r^y.r)?(r<y.r):(l<y.l);
}
}a[N];
int f[N],ans;
inline int calc(int l,int r){
return (r-l+2)*(r-l+2)/4;
}
int main(){
n=rd();
for(int i=1;i<=n;i++){
int x=rd(),y=rd(),p=rd();
a[i].l=x+y+1,a[i].r=x-y-1,a[i].p1=p,a[i].p2=0,a[i].c=calc(x+y+1,x-y-1);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i].l<=a[j].l&&a[i].r>=a[j].r)a[i].p2+=a[j].p1;
std::sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
f[i]=a[i].p2-a[i].c;
int now=1,sum=0;
for(int j=1;j<i;j++){
if(a[j].l<a[i].l&&a[j].r>=a[i].l){
while(now<=i&&a[now].r<=a[j].r){
if(a[now].l>=a[i].l)sum+=a[now].p1;
now++;
}
f[i]=max(f[i],f[j]+a[i].p2-a[i].c-(sum-calc(a[i].l,a[j].r)));
}
if(a[j].r<a[i].l)f[i]=max(f[i],f[j]+a[i].p2-a[i].c);
}
}
for(int i=0;i<=n;i++)ans=max(ans,f[i]);
printf("%d
",ans);
return 0;
}
[JSOI2010]排名
对于第一问,从 (i) 向非零的 (a_i) 连边,用一个大根堆去跑拓扑排序,把先遇到的点的排名往后排。
对于第二问,从非零的 (a_i) 向 (i) 连边,遇到 (a_i=0) 的 (i) 最开始就可以扔进大根堆,然后直接遍历,把先遇到的点的排名往前排。
#include <cstdio>
#include <queue>
#include <cstring>
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const int N=200002;
struct Edge{
int to,next;
}edge[N<<1];
int n,a[N];
int head[N],cnt,in[N];
int ans[N],now;
inline void add(int f,int t){
edge[++cnt].next=head[f];
edge[cnt].to=t;
head[f]=cnt;
}
inline void init(){
memset(head,0,sizeof head);
}
inline void work1(){
std::priority_queue<int> q;
init();
for(int i=1;i<=n;i++){
if(!a[i])continue;
add(i,a[i]),in[a[i]]++;
}
for(int i=1;i<=n;i++)
if(!in[i])q.push(i);
now=n;
while(!q.empty()){
int u=q.top();q.pop();
ans[u]=now--;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(!--in[v])q.push(v);
}
}
for(int i=1;i<=n;i++)printf("%d%c",ans[i],"
"[i==n]);
}
inline void work2(){
std::priority_queue<int> q;
init();
for(int i=1;i<=n;i++){
if(!a[i])q.push(i);
else add(a[i],i);
}
now=1;
while(!q.empty()){
int u=q.top();q.pop();
ans[u]=now++;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
q.push(v);
}
}
for(int i=1;i<=n;i++)printf("%d%c",ans[i],"
"[i==n]);
}
int main(){
n=rd();
for(int i=1;i<=n;i++)a[i]=rd();
work1();
work2();
return 0;
}
[JSOI2011]柠檬
斜率优化 (dp)。显然每一个小段首尾的 (s_i) 要相同,并且 (s_0) 也应选择这种 (s_i)。记录 (sum_i) 表示大小为 (s_i) 的贝壳在 (1-i) 中出现的次数,设 (f_i) 代表把前 (i) 个数分段得到的最大柠檬数,可以得到以下转移方程:
把式子拆开,得到:
令 (x=2*s_j*sum_j),(y=f_{j-1}+s_j*sum_i^2),使用单调栈维护上凸包。实现可以使用 (vector) 以节约空间。
注意代码中的变量名和题解中的不太一样。
#include <cstdio>
#include <vector>
typedef long long ll;
inline ll rd(){
ll x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
inline ll max(ll x,ll y){return x>y?x:y;}
const int N=100002,C=10002;
int n;
ll a[N],lst[N],s[N];
ll f[N];
std::vector <int> q[C];
inline ll sqr(ll x){return x*x;}
inline ll X(int i){return 2*a[i]*s[i];}
inline ll Y(int i){return f[i-1]+a[i]*sqr(s[i]);}
inline double slope(int i,int j){return 1.0*(Y(i)-Y(j))/(X(i)-X(j));}
#define top q[now].size()-1
int main(){
n=rd();
for(int i=1;i<=n;i++)a[i]=rd(),s[i]=s[lst[a[i]]]+1,lst[a[i]]=i;
for(int i=1;i<=n;i++){
int now=a[i];
while(q[now].size()>1&&slope(q[now][top],q[now][top-1])<slope(q[now][top],i))q[now].pop_back();
q[now].push_back(i);
while(q[now].size()>1&&slope(q[now][top],q[now][top-1])<s[i]+1)q[now].pop_back();
f[i]=f[q[now].back()-1]+a[i]*sqr(s[i]-s[q[now].back()]+1);
}
printf("%lld
",f[n]);
return 0;
}
[JSOI2011]分特产
组合+容斥。若没有每一个人必须有的限制,答案为 (prodlimits_{i=1}^mdbinom{a_i+n-1}{n-1})。有了限制的话考虑容斥,枚举至少有 (i) 个没有,剩下的可以随便给。所以答案为:
#include <cstdio>
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const int N=5002,S=3000;
const ll mod=1e9+7;
ll fac[N],inv[N];
int n,m;
ll a[N],ans;
inline ll fpow(ll b,ll p=mod-2){
ll ans=1,tmp=b;
while(p){
if(p&1)ans=ans*tmp%mod;
tmp=tmp*tmp%mod;
p>>=1;
}
return ans;
}
inline ll C(int n,int m){
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main(){
fac[0]=1;
for(int i=1;i<=S;i++)fac[i]=fac[i-1]*i%mod;
inv[S]=fpow(fac[S]);
for(int i=S-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
n=rd(),m=rd();
for(int i=1;i<=m;i++)a[i]=rd();
for(int i=0,p=0;i<=n;i++,p^=1){
ll t=C(n,i);
if(p)t=mod-t;
for(int j=1;j<=m;j++)t=t*C(n-i-1+a[j],n-i-1)%mod;
ans=(ans+t)%mod;
}
printf("%lld
",ans);
return 0;
}
[JSOI2011]棒棒糖
建一棵主席树,查询的时候对于每个节点判断一下,如果这个区间内 (le mid) 的数的个数满足条件就往左走,如果 (>mid) 的数的个数满足就往右走。都没有就是 (0)。
#include <cstdio>
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const int N=50002;
int n,q,a[N],Size;
int rt[N];
int son[N<<5][2],val[N<<5];
#define ls son[rt][0]
#define rs son[rt][1]
#define vl son[vs][0]
#define vr son[vs][1]
inline void update(int l,int r,int &rt,int vs,int x){
rt=++Size;
ls=vl,rs=vr,val[rt]=val[vs]+1;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(l,mid,ls,vl,x);
else update(mid+1,r,rs,vr,x);
}
inline int query(int l,int r,int rt,int vs,int k){
if(l==r)return l;
int mid=(l+r)>>1;
if(val[vl]-val[ls]>k)return query(l,mid,ls,vl,k);
else if(val[vr]-val[rs]>k)return query(mid+1,r,rs,vr,k);
else return 0;
}
#undef ls
#undef rs
#undef vl
#undef vr
int main(){
n=rd(),q=rd();
for(int i=1;i<=n;i++)a[i]=rd(),update(1,n,rt[i],rt[i-1],a[i]);
while(q--){
int l=rd(),r=rd();
printf("%d
",query(1,n,rt[l-1],rt[r],(r-l+1)>>1));
}
return 0;
}
[JSOI2011]任务调度
虽然是挺裸的可并堆,但还是不想手写左偏树。。所以用 (pb\_ds) 水过去了,跑的还挺快?
#include <cstdio>
#include <algorithm>
#include <map>
#include<ext/pb_ds/priority_queue.hpp>
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const int N=300002,S=502;
int n,m,t;
int val[N];
struct node{
int val,id;
node(int i=0,int v=0){val=v,id=i;}
};
struct cmp{
bool operator () (node x,node y){
return x.val>y.val;
}
};
__gnu_pbds::priority_queue<node,cmp> q[S];
__gnu_pbds::priority_queue<node,cmp>::point_iterator it[N];
int main(){
n=rd(),m=rd(),t=rd();
while(t--){
char s[10];int x,k,w;
scanf("%s",s);
if(s[0]=='A'){
x=rd(),k=rd(),w=rd();
val[k]=w;
it[k]=q[x].push(node(k,w));
}
if(s[0]=='D'){
x=rd(),k=rd(),w=rd();
val[k]-=w;
q[x].modify(it[k],node(k,val[k]));
}
if(s[0]=='T'){
x=rd(),k=rd();
q[k].join(q[x]);
}
if(s[0]=='M'){
x=rd();
printf("%d
",q[x].top().val);
}
if(s[0]=='W'){
x=rd(),w=rd();
node tmp=q[x].top();q[x].pop();
if((!q[x].empty())&&q[x].top().val==tmp.val){
puts("ERROR");
it[tmp.id]=q[x].push(tmp);
}
else{
k=tmp.id,val[k]+=w;
it[k]=q[x].push(node(k,val[k]));
}
}
}
return 0;
}
小结
( ext{JSOI2010/2011}) 的题目主要考察了 (dp) 及其优化、图论,数论和数据结构也有所涉及。其中俄罗斯方块、游戏两题暂时不会,找零钱那题也是通过分析数据的特殊性写了假算法,正解争取以后补上。通过这两年的做题,我发现自己的思维仍然不足以完成省选难度的部分题目,而且有些算法还是掌握的不太熟练,应该多做题以掌握足够的算法、技巧等。