[JSOI2014]解题报告+小结
[JSOI2014]小结
考的主要是DP、贪心、数据结构这一些,还有网络流的题因为还没学就先暂时没写,有时间还是要把没学的东西给补上来,同时也要多做一点省选的题目提高思维能力
[JSOI2014]宅男计划
三分+贪心,可以感性理解一下这个能宅的天数与叫外卖的次数为什么是一个单峰函数,如果叫外卖叫的少,那么费用就高,叫外卖叫的多,那么给外卖小哥的钱就很多,那么我们要使它们均衡,让能宅的天数达到峰值,三分一下叫外卖的次数
然后就是贪心,对于每一次叫外卖,按照食品的价格排个序,从便宜的开始,能选多少选多少
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct node
{
int p,s;
}a[203];
int m,f,n,l,r,midl,midr;
int cmp(node nx,node ny)
{
if(nx.p==ny.p)
return nx.s>ny.s;
return nx.p<ny.p;
}
int ask(int x)
{
int sum=m-x*f,w=sum/x,las=sum-x*w,now=0,ans=0,loc=1,num;
if(sum<=0)
return 0;
for(int i=1;i<=n;i++)
{
if(a[i].s>=now&&a[i].p<=w)
num=min(w/a[i].p,a[i].s-now+1),now+=num,w-=num*a[i].p;
loc=i;
if(a[i].p>w)
break;
}
ans=now*x,las+=w*x;
for(int i=loc;i<=n;i++)
if(a[i].s>=now&&a[i].p<=las)
num=min(las/a[i].p,x),ans+=num,las-=num*a[i].p;
return ans;
}
signed main()
{
scanf("%lld%lld%lld",&m,&f,&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&a[i].p,&a[i].s);
sort(a+1,a+n+1,cmp);
l=1,r=m/f;
while(l<r)
{
midl=(l+r)/2,midr=(l+r)/2+1;
if(ask(midl)>=ask(midr))
r=midl;
else
l=midr;
}
cout<<ask(l);
return 0;
}
[JSOI2014]骑士游戏
这道题太巧妙了,我们先设\(f[i]\)表示消灭第\(i\)个怪兽所需要的最小体力,然后可以很显然的得出\(f[i]=min(k[i],s[i]+\sum_{j=1}^{r[i]}f[son[i][j]])\),看上去可以DP,但实际上并不行因为他可能有环,有后效性
然后,巧妙地点就在这里,可以发现这个式子有点像SPFA的松弛操作,于是我们先把所有的\(f[i]\)设为\(k[i]\)并入队,然后就可以进行松弛
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int rd()
{
int p=1,x=0;
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;
}
int n,s[200003],k[200003],dis[200003],x,y,vis[200003],p,sum,ans;
vector<int>l[200003],g[200003];
queue<int>q;
signed main()
{
n=rd();
for(int i=1;i<=n;i++)
{
s[i]=rd(),k[i]=rd(),x=rd();
for(int j=1;j<=x;j++)
y=rd(),l[i].push_back(y),g[y].push_back(i);
}
for(int i=1;i<=n;i++)
{
dis[i]=k[i];
q.push(i),vis[i]=1;
}
while(!q.empty())
{
p=q.front(),q.pop(),vis[p]=0;
sum=s[p];
for(int j=0;j<l[p].size();j++)
sum+=dis[l[p][j]];
if(sum<dis[p])
{
dis[p]=sum;
for(int j=0;j<g[p].size();j++)
if(vis[g[p][j]]==0)
q.push(g[p][j]),vis[g[p][j]]=1;
}
}
cout<<dis[1];
return 0;
}
[JSOI2014]支线剧情2
很显然可以用树形DP,先来设几个状态,设\(f[i]\)表示只在\(i\)存档走完所有的子树的最小代价,设\(dp[i]\)表示在\(i\)存档,并且可以在子树存档读档的最小代价
\(f[i]\)的转移很好想,就是\(\sum_{j\in son_i}f[j]+s_j*dis(i,j)\),其中\(s_j\)是\(j\)子树中的叶子个数
\(dp[i]\)的转移要相对复杂一些,先设\(dp[i]\)为\(f[i]\),然后枚举子节点,先考虑不能存档,那么就跟上面的一样为\(f[j]+s_j*dis(i,j)\),然后再考虑可以在子节点存档,那么存档后我们还要在从根走到\(i\),最短时间为\(dp[j]+dis(i,j)+dis(root,i)\),但我们还发现有一个子节点是可以不用走回头路的,所以有一个可以不用加\(dis(root,i)\),枚举一下是哪一个
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,y,w,s[1000003],f[1000003],dp[1000003];
vector<int>l[1000003],l1[1000003];
void dfs(int x1,int d)
{
if(l[x1].size()==0)
s[x1]=1;
int sum=0;
for(int j=0;j<l[x1].size();j++)
{
dfs(l[x1][j],d+l1[x1][j]);
s[x1]+=s[l[x1][j]],f[x1]+=f[l[x1][j]]+s[l[x1][j]]*l1[x1][j],sum+=min(dp[l[x1][j]]+l1[x1][j]+d,f[l[x1][j]]+s[l[x1][j]]*l1[x1][j]);
}
dp[x1]=f[x1];
for(int j=0;j<l[x1].size();j++)
dp[x1]=min(dp[x1],sum-min(dp[l[x1][j]]+l1[x1][j]+d,f[l[x1][j]]+s[l[x1][j]]*l1[x1][j])+dp[l[x1][j]]+l1[x1][j]);
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&k);
for(int j=1;j<=k;j++)
{
scanf("%lld%lld",&y,&w);
l[i].push_back(y),l1[i].push_back(w);
}
}
dfs(1,0);
cout<<dp[1];
return 0;
}
[JSOI2014]强连通图
这道题和下面的歌剧表演都算是比较简单的题了,这道题只要缩点,第一问求最大的强连通分量,第二问求的是\(max(入度为0的点数,出度为0的点数)\)
#include<bits/stdc++.h>
using namespace std;
int n,m,x,y,dfn,df[100003],low[100003],st[100003],hd,ans1,co,sum[100003],col[100003],chu[100003],ru[100003],cc,rr;
vector<int>l[100003],g[100003];
void dfs(int x1)
{
dfn++,df[x1]=low[x1]=dfn,hd++,st[hd]=x1;
for(int j=0;j<l[x1].size();j++)
if(df[l[x1][j]]==0)
{
dfs(l[x1][j]);
low[x1]=min(low[x1],low[l[x1][j]]);
}
else if(col[l[x1][j]]==0)
low[x1]=min(low[x1],df[l[x1][j]]);
if(df[x1]==low[x1])
{
co++,col[x1]=co,sum[co]++;
while(st[hd]!=x1)
col[st[hd]]=co,hd--,sum[co]++;
hd--;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
l[x].push_back(y);
}
for(int i=1;i<=n;i++)
if(df[i]==0)
dfs(i);
for(int i=1;i<=co;i++)
ans1=max(ans1,sum[i]);
cout<<ans1<<endl;
for(int i=1;i<=n;i++)
for(int j=0;j<l[i].size();j++)
if(col[i]!=col[l[i][j]])
chu[col[i]]=1,ru[col[l[i][j]]]=1;
for(int i=1;i<=co;i++)
{
if(chu[i]==0)
cc++;
if(ru[i]==0)
rr++;
}
cout<<max(cc,rr);
return 0;
}
[JSOI2014]歌剧表演
首先要思考一下要怎样才能辨认的出某一个演员\(i\),那么就是不存在这样的一个演员,出现的演出和次数都跟\(i\)一模一样
那么对于每一次的演出,我们都可以拆成若干个集合,在同一个集合里的出现的演出和次数相同,不在则不同,当一个集合只剩一个人时那这个人就可以被辨认了,这个可以用c++的set很简便的维护
#include<bits/stdc++.h>
using namespace std;
set<int>s[100003];
int n,m,k,a[100003],ans[100003],l,r,num[100003],sum=1,now;
inline int rd()
{
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')
a=getchar();
if(a=='-')
p=-p;
while(a>47&&a<58)
x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
int cmp(const int &nx,const int &ny)
{
return num[nx]<num[ny];
}
int main()
{
n=rd(),m=rd();
for(int i=1;i<=n;i++)
num[i]=1,s[1].insert(i);
for(int i=1;i<=m;i++)
{
k=rd();
for(int i=1;i<=k;i++)
a[i]=rd();
l=1,r=1;
sort(a+1,a+k+1,cmp);
while(r<=k)
{
while(num[a[r]]==num[a[l]]&&r<=k)
r++;
if(s[num[a[l]]].size()==r-l)
{
l=r;
continue;
}
sum++,now=num[a[l]];
for(int j=l;j<r;j++)
s[now].erase(a[j]),s[sum].insert(a[j]),num[a[j]]=sum;
if(s[now].size()==1&&ans[*s[now].begin()]==0)
ans[*s[now].begin()]=i;
if(s[sum].size()==1&&ans[*s[sum].begin()]==0)
ans[*s[sum].begin()]=i;
l=r;
}
}
for(int i=1;i<=n-1;i++)
printf("%d ",ans[i]);
cout<<ans[n];
return 0;
}
[JSOI2014]序列维护
前不久学习分块时做过这道题,并不是很难,只是注意乘法的时候把加法的tag也乘一下就行了
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100003],pos[100003],mul[100003],tag[100003],typ,l,r,c,m,s[100003],su,mod,o,ss[100003];
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;
}
void down(int l1,int r1)
{
for(int i=(pos[l1]-1)*m+1;i<=min(n,pos[l1]*m);i++)
ss[pos[i]]-=a[i],a[i]*=mul[pos[i]],a[i]%=mod,a[i]+=tag[pos[i]],a[i]%=mod,ss[pos[i]]=(ss[pos[i]]+a[i]+mod)%mod;
tag[pos[l1]]=0,mul[pos[l1]]=1;
if(pos[l1]!=pos[r1])
{
for(int i=(pos[r1]-1)*m+1;i<=min(n,pos[r1]*m);i++)
ss[pos[i]]-=a[i],a[i]*=mul[pos[i]],a[i]%=mod,a[i]+=tag[pos[i]],a[i]%=mod,ss[pos[i]]=(ss[pos[i]]+a[i]+mod)%mod;
tag[pos[r1]]=0,mul[pos[r1]]=1;
}
}
void addja(int l1,int r1,int num)
{
down(l1,r1);
for(int i=l1;i<=min(r1,pos[l1]*m);i++)
ss[pos[i]]-=a[i],a[i]+=num,a[i]%=mod,ss[pos[i]]=(ss[pos[i]]+a[i]+mod)%mod;
if(pos[l1]!=pos[r1])
for(int i=(pos[r1]-1)*m+1;i<=r1;i++)
ss[pos[i]]-=a[i],a[i]+=num,a[i]%=mod,ss[pos[i]]=(ss[pos[i]]+a[i]+mod)%mod;
for(int i=pos[l1]+1;i<=pos[r1]-1;i++)
tag[i]+=num,tag[i]%=mod;
}
void addch(int l1,int r1,int num)
{
down(l1,r1);
for(int i=l1;i<=min(r1,pos[l1]*m);i++)
ss[pos[i]]-=a[i],a[i]*=num,a[i]%=mod,ss[pos[i]]=(ss[pos[i]]+a[i]+mod)%mod;
if(pos[l1]!=pos[r1])
for(int i=(pos[r1]-1)*m+1;i<=r1;i++)
ss[pos[i]]-=a[i],a[i]*=num,a[i]%=mod,ss[pos[i]]=(ss[pos[i]]+a[i]+mod)%mod;
for(int i=pos[l1]+1;i<=pos[r1]-1;i++)
tag[i]*=num,tag[i]%=mod,mul[i]*=num,mul[i]%=mod;
}
int ask(int l1,int r1)
{
down(l1,r1);
int sum=0;
for(int i=l1;i<=min(r1,pos[l1]*m);i++)
sum=(sum+a[i])%mod;
if(pos[l1]!=pos[r1])
for(int i=(pos[r1]-1)*m+1;i<=r1;i++)
sum=(sum+a[i])%mod;
for(int i=pos[l1]+1;i<=pos[r1]-1;i++)
sum=(sum+ss[i]*mul[i]%mod+tag[i]*s[i]%mod)%mod;
return sum;
}
signed main()
{
n=rd(),mod=rd();
for(int i=1;i<=n;i++)
a[i]=rd();
m=sqrt(n);
for(int i=1;i<=n;i++)
pos[i]=(i-1)/m+1,mul[pos[i]]=1,s[pos[i]]++,ss[pos[i]]+=a[i];
su=pos[n];
o=rd();
for(int i=1;i<=o;i++)
{
typ=rd(),l=rd(),r=rd();
if(typ==2)
{
c=rd();
addja(l,r,c);
}
else if(typ==1)
{
c=rd();
addch(l,r,c);
}
else
printf("%lld\n",ask(l,r));
}
return 0;
}