题目大意就是有n个学生和m个学科,第i个学生希望所有学科的成绩在$t_{i}$及之前出来,不然他就会产生C*(最晚的学科出来的时间-$t_{i}$)的不开心值。
现在第i个学科出来的时间有一个初步计划$b_{I}$,但是为了减少不愉快值,有两种操作:使一个学科成绩早出来一天并产生B的不开心值,使一个学科早出来一天并且一个学科晚出来一天且产生A的不开心值。
操作可以无限使用,求最小的不开心值。
一个同学不会不开心当且仅当所有学科成绩都在他期望的时间内出现,那么就意味着单纯地调整中间的学科而不是最晚的学科是无济于事的。
那么就可以考虑枚举最晚的学科出现的时间,最初在在这个时间线右边的科目都需要调整(这个调整肯定是贪心的都恰好移动到这个时间线就好了,因为再移动只要最后一个学科在这个时间线都没用)
考虑调整的策略:如果A>=B那么就不用A,因为达到同样的效果B产生的不开心值小而且还不会影响别人。
不然的话就要用A了,可以往后移动的就是在枚举的时间线左边的学科,每个这样的学科都最多移动到这个时间线,当然如果左边的移完了都还没有达到要求就只有用B。
通过上面的分析可以知道需要用到的数组:lval[],rval[]分别表示左边(右边)的学科到i这个时间线的距离和,因为需要判断移动次数。
暴力的话是$O(n^{2})$的看到不能接受,考虑能不能递推。
例如左边:
当我们从i-1这个时间线移动到i这个时间线时:
所有在i-1左边的学科到i的距离都会+1,并且i还需要+在i的学科数。
所以就知道还需要一个数组lnum[]记录i左边的学科数。
右边同理,于是lval和rval就可以$O(n)$递推了。
但是不开心的人的不开心值怎么求,可以发现是一样的道理,记录一个到时间线的距离和就好了,*C就是不开心值。
#include<ctime> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=100005; const int oo=1000000000; const ll inf=10000000000000000; ll A,B,C; int n,m; int t[maxn],b[maxn]; int c[maxn],lnum[maxn],rnum[maxn],cp[maxn];//num:时间线 ll lval[maxn],rval[maxn],ans,lcnt[maxn],lsum[maxn];//cnt:人数 template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch==‘-‘);ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } ll get(int i){ if(A<B){ if(lval[i]>=rval[i]) return A*rval[i]; return A*lval[i]+B*(rval[i]-lval[i]); } return B*rval[i]; } void planb(){ for(int i=1;i<=m;i++) c[b[i]]++; for(int i=1;i<=n;i++) cp[t[i]]++; for(int i=2;i<=b[m];i++){ rnum[1]+=c[i]; rval[1]+=1ll*c[i]*(i-1); } for(int i=2;i<=maxn-5;i++){ lnum[i]=lnum[i-1]; lval[i]=lval[i-1]; lval[i]+=lnum[i-1]; lnum[i]+=c[i-1]; lval[i]+=c[i-1]; rnum[i]=rnum[i-1]; rval[i]=rval[i-1]; rnum[i]-=c[i]; rval[i]-=c[i]; rval[i]-=rnum[i]; lcnt[i]=lcnt[i-1]; lsum[i]=lsum[i-1]; lsum[i]+=lcnt[i-1]; lcnt[i]+=cp[i-1]; lsum[i]+=cp[i-1]; } ll ret=inf; for(int i=1;i<=t[1];i++) ret=min(ret,get(i)); if(C!=inf) return ; printf("%lld",ret); exit(0); } void plana(){ ll ret; sort(b+1,b+m+1); sort(t+1,t+n+1); if(C==inf) planb(); for(int i=1;i<=n;i++) if(t[i]<b[m]) ans+=C*(b[m]-t[i]); ret=ans; planb(); for(int i=maxn-5;i;i--) ret=min(ret,lsum[i]*C+get(i)); printf("%lld",ret); } int main(){ freopen("exam.in","r",stdin); freopen("exam.out","w",stdout); read(A);read(B);read(C); read(n);read(m); for(int i=1;i<=n;i++) read(t[i]); for(int i=1;i<=m;i++) read(b[i]); plana(); } /* 100 100 2 4 5 5 1 2 3 1 1 2 3 3 3 5 4 5 6 2 2 5 8 9 3 4 4 2 9 3 */
其实lnum和lcnt就是c和cp的前缀和,所以cp似乎可以不用。
在考试的时候一开始想到枚举时间就只是人的时间,后来发现如果你C太大的话从这个时间线再向左移动可能会得到更优的解(其实就是样例过不去),结果改完之后rval的初始化还是写的i-t[1],所以最小的期望时间不是1就爆炸了。
还有就是C=1e16随便乘一下就GG了,所以特判就over了。
还有一种方法是三分,这是个单峰函数,可是我不怎么会。
大意:给出一个长度为n的序列,有两种操作:将区间[l,r]的每个数数变成$c^{x}$,对[l,r]的数求和对p取模。
看了一会题就发现与上帝与集合的正确用法有着异曲同工之妙,只不过是这道题不是无穷下去,可是我没做那道题........
那就先来分析 上帝与集合 :
扩展欧拉定理: $a^{b} mod p = a^{b \% \phi (p) + \phi (p)} (b>\phi (p))$
$b<\phi (p) $就直接算就好了.
因为是无穷下去所以b一定是大于$\phi(p)$的,然后就可以化为子问题,考虑什么时候可以终止,那便是p==1时,因为无论再怎么搞mod了之后都是1,那就解决了。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=10000000; int t; int prime[700005],phi[maxn+6]; bool not_prime[maxn+6]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch==‘-‘);ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } void init(){ for(int i=2;i<=maxn;i++){ if(!not_prime[i]){ prime[++prime[0]]=i; phi[i]=i-1; } for(int j=1;j<=prime[0]&&i*prime[j]<=maxn;j++){ not_prime[i*prime[j]]=true; if(!(i%prime[j])){ phi[i*prime[j]]=phi[i]*prime[j]; break; } else phi[i*prime[j]]=phi[i]*(prime[j]-1); } } } int fpow(ll a,int b,int mod){ ll ret=1; while(b){ if(b&1) ret=ret*a%mod; a=a*a%mod; b>>=1; } return ret%mod; } int dfs(int p){ if(p==1) return 0; return fpow(2,dfs(phi[p])+phi[p],p); } void cx(){ int p;read(p); printf("%d\n",dfs(p)); } int main(){ read(t); init(); while(t--) cx(); }
然后看这道题,一样的思路可以发现当递归到一定层数的时候值就不变了(p==1),并且只会递归log次,因为求$\phi$的时候一个偶数至少减少一半,而一个奇数就会变成偶数(根据欧拉函数的求法)
记这个层数为k,我们每次对一个位置暴力修改(重新求值),那么当一个位置被修改了k次之后就不用了修改他了,这个思路又和这个题一样。
首先预处理出p求i次$\phi$的值phi[],然后每次修改递归求就好了,不一样的是需要判断是否+$\phi (p)$,所以在用以指数为底数的快速幂中需要记录是否大于$\phi (p)$,返回二元组即可。
一次修改$O(lognlog^{2}p)$,这是比较难受的,但是可以预处理出两个数组c1,c2表示c^i和c^i*10000(对于每个phi[]都要),那么对于一个c的次方就可以通过拼接来表示(一开始看的题解都只预处理到1e8,但是刚刚想了一下指数最大好像是2p,所以预处理2e8稳一点)。
还要预处理是否大于phi[],那么修改就可以$O(lognlogp)$了。
一个细节就是phi最后需要两个1,比如这一位是0,c=2,,p=4,phi就是2,1,然后对这个位置修改三次的话只会算两次也就是得到2,但是会先答案是0。
考虑p=1停止的实质就是如果再递归下去,当x>=1时返回的也是0+1。但是会发现x=0时返回0,就会有错误。(感性理解?)
#include<ctime> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=5005; const int maxc=20000; int n,m,cnt,k; ll c,p,a[maxn]; int root,ls[maxn<<1],rs[maxn<<1],mi[maxn<<1],phi[100]; ll sum[maxn<<1],cc[maxc+5][65][2]; bool cx[maxc+5][65][2]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch==‘-‘);ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } int get_phi(int x){ int ans=x; for(int i=2;i*i<=x;i++){ if(x%i==0){ ans=ans/i*(i-1); while(x%i==0) x/=i; } } if(x!=1) ans=ans/x*(x-1); return ans; } void update(int rt){ sum[rt]=(sum[ls[rt]]+sum[rs[rt]])%p; mi[rt]=min(mi[ls[rt]],mi[rs[rt]]); } void build(int &rt,int l,int r){ rt=++cnt; if(l==r) { mi[rt]=sum[rt]=a[l]; return ; } int mid=(l+r)>>1; build(ls[rt],l,mid); build(rs[rt],mid+1,r); update(rt); } ll get(ll val,int s,int tot){ if(s==tot) return val > phi[tot] ? val % phi[tot] + phi[tot] : val ; ll now=get(val,s+1,tot);//指数 ll x= cc[now/maxc][s][1]*cc[now%maxc][s][0] ; bool opt= cx[now/maxc][s][1]|cx[now%maxc][s][0] ; return x + opt*phi[s]; } void modify(int rt,int l,int r,int a_l,int a_r){ if(mi[rt]>=k) return ; if(l==r){ mi[rt]++; sum[rt]=get(a[l],1,mi[rt]); return ; } int mid=(l+r)>>1; if(a_l<=mid) modify(ls[rt],l,mid,a_l,a_r); if(mid<a_r) modify(rs[rt],mid+1,r,a_l,a_r); update(rt); } ll query(int rt,int l,int r,int a_l,int a_r){ if(a_l<=l&&r<=a_r) return sum[rt]; ll ret=0; int mid=(l+r)>>1; if(a_l<=mid) ret+=query(ls[rt],l,mid,a_l,a_r); if(mid<a_r) ret+=query(rs[rt],mid+1,r,a_l,a_r); return ret%p; } void init(){ phi[0]=p; for(int i=1;;i++){ phi[++k]=get_phi(phi[i-1]); if(phi[i]==1) break; } for(int i=0;i<=k;i++){ cc[0][i][1]=cc[0][i][0]=1; if(cc[0][i][0]>=phi[i]) { cx[0][i][1]=cx[0][i][0]=true; cc[0][i][0]-=phi[i]; cc[0][i][1]-=phi[i]; } } for(int i=1;i<=maxc;i++) for(int j=0;j<=k;j++){ cc[i][j][0]=cc[i-1][j][0]*c; cx[i][j][0]=cx[i-1][j][0]; if(cc[i][j][0]>=phi[j]){ cc[i][j][0]%=phi[j]; cx[i][j][0]=true; } } for(int i=0;i<=k;i++){ cc[1][i][1]=cc[maxc][i][0]; cx[1][i][1]=cx[maxc][i][0]; } for(int i=2;i<=maxc;i++) for(int j=0;j<=k;j++){ cc[i][j][1]=cc[i-1][j][1]*cc[1][j][1]; cx[i][j][1]=cx[i-1][j][1]; if(cc[i][j][1]>=phi[j]){ cc[i][j][1]%=phi[j]; cx[i][j][1]=true; } } } int main(){ //freopen("verbinden.in","r",stdin); //freopen("verbinden.out","w",stdout); read(n);read(m);read(p);read(c); init(); for(int i=1;i<=n;i++) read(a[i]); build(root,1,n); while(m--){ int opt,l,r; read(opt);read(l);read(r); if(!opt) modify(1,1,n,l,r); else printf("%lld\n",query(1,1,n,l,r)); } }
很巧妙的思路:可以发现他选的个数mod k=r,那么问题就是从nk个物品里面选出mod k=r个物品的方案树。
f[i][j]前i个物品里面选取mod k=j个物品的方案数,f[i][j]=f[i-1][j]+f[i-1][(j-1+k)%k]。
n这么大的范围肯定要用矩阵加速,而且转移是一维同时转移,考虑如何从f[i-1]转移到f[i]。
还是比较好想,a[i][i]和a[i][(i-1+k)%k]都为1即可,比较好理解吧。
有一种特殊情况就是k=1,会发现如果直接赋值两次都赋的同一位置,所以使用++或者特判。(++很方便emmm)
//原式为在n*k个物品内选取mod k余数为r的方案数 //f[i][j]:前i个物品选取mod k余数为j的方案数 //f[i][j]=f[i-1][j]+f[i-1][j-1] #include<ctime> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long ll n,p; int k,r; struct matrix{ ll a[55][55]; matrix(){memset(a,0,sizeof(a));} }base; matrix operator * (matrix x,matrix y){ matrix ret; for(int i=0;i<k;i++) for(int j=0;j<k;j++) for(int l=0;l<k;l++) ret.a[i][j]=(ret.a[i][j]+x.a[i][l]*y.a[l][j])%p; return ret; } matrix fpow(matrix x,ll b){ matrix ret=base; while(b){ if(b&1) ret=ret*x; x=x*x; b>>=1; } return ret; } template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch==‘-‘);ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } int main(){ freopen("problem.in","r",stdin); freopen("problem.out","w",stdout); read(n);read(p);read(k);read(r); for(int i=0;i<k;i++) base.a[i][i]=1; matrix ans,ret; ans.a[0][0]=1; for(int i=0;i<k;i++){ ret.a[i][i]++; ret.a[(i+k-1)%k][i]++; } printf("%d",(ans*fpow(ret,n*k)).a[0][r]); }
(树形DP,待填坑。。。。)
考虑一个位置最多动一下,(动两下相当于不动,考试没想到),那么k=n就可以知道是啥了,就是找出策略使得灯都关闭。
那么考虑一个位置动或不动只可能和他后面的灯有关系,所以到着看,如果是亮的就必须关,然后把他的约数位置的状态取反。
nlogn预处理出约数的话单凭这个就可以得80,因为如果步数cnt比k小仍然输出cnt。
考虑cnt>k,从刚才的分析可以知道需要动的位置其实是确定的,不存在多个解。
那么设f[i]为有i个位置需要动的时候期望多少步结束。
$f[i]=\frac{i}{n} (f[i-1]+1) + \frac{n-i}{n} (f[i+1]+1)$
然后高斯消元求解,$O(n^{3})$
考虑线性,设f[i]是有i个需要动的位置的时候期望多少步到i-1个需要动的位置。
$f[i]=\frac{i}{n} *1+ \frac{n-i}{n} (f[i]+f[i+1]+1)$
$f[i]=1+ \frac{n-i}{i} (f[i+1]+1)$
最后对f[k+1]+...+f[cnt]+k求和即可。
至于为什么会想到,我也不知道,第二种是第一种的差分(或许多做会好点?)
#include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=100005; const int mod=100003; #define ll long long int n,k,lim,a[maxn]; ll ans,inv[maxn],f[maxn]; //f:从有i个需要动的灯到有i-1个需要动的灯的期望操作数 vector<int> c[maxn]; int main(){ freopen("trennen.in","r",stdin); freopen("trennen.out","w",stdout); scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) for(int j=1;i*j<=n;j++) c[i*j].push_back(i); for(int i=n;i;i--) if(a[i]){ lim++; for(unsigned int j=0;j<c[i].size();j++) a[c[i][j]]^=1; } if(lim<=k) ans=lim; else { inv[1]=1; for(int i=2;i<=n;i++) inv[i]=((mod-mod/i)*inv[mod%i]%mod+mod)%mod; f[n]=1; for(int i=n-1;i>k;i--) f[i]=(1+(n-i)*(1+f[i+1])%mod*inv[i])%mod; for(int i=lim;i>k;i--) ans=(ans+f[i])%mod; ans=(ans+k)%mod; } for(int i=1;i<=n;i++) ans=ans*i%mod; printf("%lld",ans); }
选取[l,r]的食物,那么就会获得所有小区间的值,也就是如果要获得[l,r]的美味度,那么就要获得它包含的小区间的美味度。
这就是最大权闭合子图问题。
所以考虑对每个美味度的区间都开一个点,每个点指向他包含的小区间,边权为inf(只用指向长度小1的即可,更小的可以间接指向)
对于每个美味度<0的区间指向T,不然S指向该区间,边权为美味度的绝对值(套路)
现在考虑费用问题,看到m*x^2只和代号有关,所以对于每个代号开个点,向T连边,边权为m*x^2。
对于c*x,对于单个寿司向T连边,边权为代号;向代号连边,边权为inf。这就表示如果选了这个寿司,那么就会有m*x^2。
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=105; const int maxm=100005; const int inf=0x3f3f3f; int n,m,s,t,ans; int a[maxn],cx[maxn][maxn],id[maxn][maxn],lsh[1005];//id:区间编号 lsh:代号编号 int cnt,head[maxn*maxn]; struct edge{ int x,y,next,val; }e[maxm]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch==‘-‘);ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } void add_edge(int x,int y,int val){ e[++cnt]=(edge){x,y,head[x],val}; head[x]=cnt; } void add(int x,int y,int val){ add_edge(x,y,val); add_edge(y,x,0); } //dinic int d[maxn*maxn]; bool bfs(){ queue<int> q; memset(d,0,sizeof(d)); q.push(s);d[s]=1; while(!q.empty()){ int x=q.front(); q.pop(); for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(!d[y]&&e[i].val){ d[y]=d[x]+1; if(y==t) return true; q.push(y); } } } return false; } int dfs(int x,int flow){ if(x==t) return flow; int rest=flow,k; for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(e[i].val&&d[y]==d[x]+1){ k=dfs(y,min(e[i].val,rest)); if(!k) {d[y]=0;continue;} e[i].val-=k; e[i^1].val+=k; rest-=k; if(!rest) return flow; } } return flow-rest; } int dinic(){ int ret=0; while(bfs()) ret+=dfs(s,inf); return ret; } /*----------------------------------------------------------*/ void make_edge(){ //区间与源汇点 for(int i=1;i<=n;i++) for(int j=i;j<=n;j++){ if(cx[i][j]>=0) add(s,id[i][j],cx[i][j]);//权值>0就从源点连边,割掉就是不选 else add(id[i][j],t,-cx[i][j]);//否则就向汇点连边,割掉就是要选 } //区间与区间,选一个区间就必须要有它包含的小区间 for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++){ add(id[i][j],id[i][j-1],inf); add(id[i][j],id[i+1][j],inf); } //单个寿司与代号和汇点连边 for(int i=1;i<=n;i++){ add(id[i][i],t,a[i]); add(id[i][i],lsh[a[i]],inf); } //代号和汇点 for(int i=0;i<=1000;i++) if(lsh[i]) add(lsh[i],t,m*i*i); } int main(){ freopen("sushi.in","r",stdin); freopen("sushi.out","w",stdout); read(n);read(m); for(int i=1;i<=n;i++){ read(a[i]); if(!lsh[a[i]]) lsh[a[i]]=++cnt; } for(int i=1;i<=n;i++) for(int j=i;j<=n;j++){ read(cx[i][j]); id[i][j]=++cnt; ans+= cx[i][j]>0 ? cx[i][j] : 0 ; } s=0;t=cnt+1;cnt=1; make_edge(); ans-=dinic(); printf("%d",ans); }
原文:https://www.cnblogs.com/sto324/p/11560064.html