还是来写点有意思的题目的题解吧/x
C.Rabbit的工作(1)
这题很明显是个dp题,因为n很小,我们直接乱搞就行了,不用考虑优化什么的。。。
我们设dp[i][j]表示前i天,我们一共工作了j天所耗费的最小疲惫度
那么对于第i天来说,我们有如下情况:
如果第i天为0,直接从i-1那里将值继承过来就行了,即dp[i][j]=dp[i-1][j]
如果第i天为1,那么就有两个情况:
1.第i天不上课,那么和上面的一样,dp[i][j]=dp[i-1][j]
2.第i天上课,但是,我们无法保证连续性啊,难道我们还要多加一维0/1表示第i是否上课吗?
不用,我们直接乱搞,我们直接枚举我们从哪天开始到第i天连续工作就行了,我们设我们枚举的从第k天到第i天连续工作,那么,我们要从k-2开始更新,为啥?因为如果我们从第k-1天更新的话,就会使得如果我们第k-1天工作的话,就和现在的k到i天工作连续起来了,但是我们转移的时候,算贡献却只算了k-j+1天连续工作的贡献,就会使得答案偏小。
也即是,dp[i][j]=min(dp[i][j],dp[k-2][j-(i-k+1)]+sum(i-k+1))(其中,i-k都是1且k>=2,sum(x)表示1+2+...+x的值)
如果k=1的话,直接特判就好了,即dp[i][i]=min(dp[i][i],sum(i))
然后,这题就解完了。
代码:
#include<bits/stdc++.h> using namespace std; const int N=401; int dp[N][N]; int main(){ int n,k; scanf("%d%d",&n,&k); string x; cin>>x; memset(dp,0x3f3f,sizeof(dp)); dp[0][0]=0; for(int i=1;i<=n;++i){ for(int j=0;j<i;++j){ dp[i][j]=dp[i-1][j]; } if(x[i-1]=='1'){ for(int j=i;j;--j){//枚举第j->i天工作 if(x[j-1]=='0')break; int t=i-j+1,sum=(1+t)*t/2; if(j==1){ dp[i][t]=min(dp[i][t],sum); }else{ for(int k=t;k<=i;++k){ dp[i][k]=min(dp[i][k],dp[j-2][k-t]+sum); } } } } } for(int i=n;~i;--i){ if(dp[n][i]<=k){ printf("%d",i); return 0; } } return 0; }
Update:
看了@ 神崎兰子大佬的做法后恍然大悟,于是优化到了(大雾)
因为上限在排序,带个log,但是值都很小,于是直接上桶排!(若差值较大,请使用松式基排)
代码:
#include<bits/stdc++.h> using namespace std; const int N=401,M=256,mod=255; int d[4][N],f[N]; int a[N],e,b[N*101],c[N]; inline int calc(int x,int y){//计算连续x个可工作日中工作y天的最小疲惫度 if(x<y)return 2e9; int A=x-y+1;//A段 if(A>=y)return y; int B=y/A,C=y%A; int res=A*(1+B)*B/2+C*(B+1); return res; } inline void bad_sort(int l,int r){//直接上暴力桶排,如果差距过大,直接使用基数排序即可 int maxe=0; for(int i=l;i<=r;++i){ ++b[c[i]];maxe=max(maxe,c[i]); } int tot=l-1; for(int i=1;i<=maxe;++i){ while(b[i]){ c[++tot]=i;--b[i]; } } } inline void base_sort(int n){ for(int i=1;i<=n;++i){ ++d[0][c[i]&mod];++d[1][(c[i]>>8)&mod]; ++d[2][(c[i]>>16)&mod];++d[3][(c[i]>>24)&mod]; } for(int i=1;i<M;++i){ d[0][i]+=d[0][i-1];d[1][i]+=d[1][i-1]; d[2][i]+=d[2][i-1];d[3][i]+=d[3][i-1]; } for(int i=n;i;--i){ f[d[0][c[i]&mod]--]=c[i]; } for(int i=n;i;--i){ c[d[1][(c[i]>>8)&mod]--]=f[i]; } for(int i=n;i;--i){ f[d[2][(c[i]>>16)&mod]--]=c[i]; } for(int i=n;i;--i){ c[d[3][(c[i]>>24)&mod]--]=f[i]; } } int main(){ int n,k; scanf("%d%d",&n,&k); string x; cin>>x; for(int i=1;i<=n;++i){ if(x[i-1]=='1'){ int res=0; for(int j=i;j<=n;++j){ if(x[j-1]=='0')break; ++res; } a[++e]=res;i+=(res-1); } } int tot=0; for(int i=1;i<=e;++i){//运行次数=1的个数<=n int pas=0; for(int j=1;j<=a[i];++j){ int now=calc(a[i],j); c[++tot]=now-pas;pas=now; } } bad_sort(1,tot); //base_sort(tot); int ans=0; for(int i=1;i<=tot;++i){ if(k<c[i])break;++ans;k-=c[i]; } printf("%d",ans); return 0; }
一个猜想(下午来试着证明下)
设一共有x天可以连续加班,我们一共加班y天的最小代价为calc(x,y)
那么有,其中1<y<x
证明:
如果,
明显成立。(左边=1,右边也一定大于等于1)
考虑其它可能性
我们设每段连续工作的天数分别为(工作y-1天):
那么,一定有
那么总代价就是
若我们工作y天,那么,现在我们就发现我们需要将k段变成k-1段,并将和变为y
那么在使用最小代价的策略下,不难发现,我们必然是将平均分配给剩余的几个数(优先分配给前面的),也就是说,现在的工作段数变为了:
那么,计算下变化代价就是:
同理,我们计算出y->y+1的变化代价:
(为了方便计算,这里直接使用放缩,也即是,按道理,本来中已经有多个数字变大了,但这里我们先默认不变)
下减上,我们把中间的一大堆先照抄(看起来太麻烦了qwq),把(b_2+1)和后面的求和合并下就有:
注意到,由于,所以,除了最右边那个-1以外的那堆东西,它们的"加的项数">="减的项数",而且,因为加的项数是每k-2个就翻一翻(也即是增长的要快些),所以,加的和>=减的和
也即是,说,该式的值应该。
但是-1明显不是我们所想要的,但是,注意到,我们之前使用了“放缩法”,也即是说,实际上,我们y->y+1变化的值是大于目前的值的,又由于变化值肯定是个整数,所以,就有:
y->y+1变化值-y->y-1变化值>-1 &&y->y+1变化值和y->y-1变化值都是整数
那么,就不难有:
y->y+1变化值-y->y-1变化值>=0成立了
用calc表达就是我们需要证明的东西了!
证毕后的我:
证明了这个结论后有啥用呢?
我的算法是:我们如果将所有阶段的变化值按小到大排序的话,那么,我们依次将变化值加入,直到加入后会大于k为止,那么,加入的变化值的个数就是答案数
如果证明了这个的话,那么,我们就可以知道,我们一定是会先加入y-1->y后才会加入y->y+1,而不会没加入y-1->y就加入y->y+1而使得答案出错
D.华华和月月逛公园
其实就是求,有多少条边满足删除后剩下的图不连通。
其实就是个简单的tarjan缩环问题,但是,我们的是无向图啊,该怎么办呢?
Ps:如果是单向边的话,其实就是等价求,有多少条边参与构成了任意一个环(yy就行辣)
无向图缩环了解一下。(我大号终于又暴露了,2333)
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e6+1; struct node{ int v,nex; }t[N<<1]; int len,las[N]; bool vis[N];int dfn[N],low[N],sta[N],top,cnt; int fa[N];int U[N],V[N]; bool kil[N]; inline void add(int u,int v){ t[++len]=(node){v,las[u]},las[u]=len; } inline void tarjan(int x){ dfn[x]=low[x]=++cnt;sta[++top]=x,vis[x]=1; for(int i=las[x];i;i=t[i].nex){ if(kil[i])continue;kil[i^1]=1; int v=t[i].v; if(!dfn[v]){ tarjan(v); low[x]=min(low[x],low[v]); }else if(vis[v]){ low[x]=min(low[x],dfn[v]); } } if(dfn[x]==low[x]){ while(true){ int v=sta[top--];vis[v]=0;fa[v]=x; if(v==x)break; } } } int main(){ int n,m;len=1; scanf("%d%d",&n,&m); for(int i=1;i<=m;++i){ int u,v; scanf("%d%d",&u,&v); U[i]=u,V[i]=v; add(u,v),add(v,u); } for(int i=1;i<=n;++i){ if(!dfn[i]){ tarjan(i); } } int res=0; for(int i=1;i<=m;++i){ res+=(fa[U[i]]==fa[V[i]]); } printf("%d",res); return 0; }