【浮*光】#省选真题# [六省联考2017]
T1:【p3745】期末考试
- n位同学每人参加m场考试,第i位同学想在ti天之前知道所有成绩,延后一天unhappy+=C;
- 对于第i门课程,计划在第bi天公布成绩。有两条调整公布时间的措施:
- 1.课程x,y的天数分别-1,+1,unhappy+=A; 2.课程z天数-1,unhappy+=B。求 unhappy MIN。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3745】期末考试 n位同学每人参加m场考试,第i位同学想在ti天之前知道所有成绩,延后一天unhappy+=C; 对于第i门课程,计划在第bi天公布成绩。有两条调整公布时间的措施: 1.课程x,y的天数分别-1,+1,unhappy+=A; 2.课程z天数-1,unhappy+=B。求 unhappy MIN。*/ //【标签】思维题 + 分析结论 + dp /* 1. 点1,2,不用修改操作,直接求sum_unhappy即可,10pts; 2. 点3,4,只有-1+1操作,10pts; 3.点5,6,7,8,B<A,一定选用-1操作,20pts; /*【分析】所有成绩的最终发出时间是一样的,设为sum_天,sum_=max{bi} 即:将ti排序,记录ti数组前缀和sumt[i],sum_二分到最后延后的位置i, 则∑C=sum_*i-sumt[i]。其实我特别想枚举sum_,最大只有10^5... */ void reads(ll &x){ //读入优化(正负整数) ll fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const ll N=100019; ll A,B,C,n,m,student[N],exam[N]; ll maxa,mina=N,maxb,need,rest,sumc,ans=(ll)1e16; struct node{ ll t,b,b_; }a[N]; ll work(ll k1,ll k2,ll k3){ if(k1<=k2) return k3*C+k1*A; if(k1>k2) return k3*C+k2*A+(k1-k2)*B; } int main(){ reads(A),reads(B),reads(C); reads(n),reads(m); A=min(A,B); for(ll i=1,ti;i<=n;i++) reads(ti),student[ti]++, maxa=max(maxa,ti),mina=min(mina,ti); for(ll i=1,bi;i<=m;i++) reads(bi),exam[bi]++,maxb=max(maxb,bi); for(ll i=1;i<=maxa;i++) a[i].t=a[i-1].t+student[i]; //某天unhappy的人数 for(ll i=1;i<=maxb;i++) a[i].b=a[i-1].b+exam[i]; //某天完成的bi总数 for(ll i=maxb;i>=mina;i--) a[i].b_=a[i+1].b_+exam[i],need+=a[i].b_; for(ll i=1;i<mina;i++) rest+=a[i].b; for(ll i=mina;i<=maxb;i++){ //枚举sum_ need-=a[i].b_,ans=min(ans,work(need,rest,sumc)); rest+=a[i].b,sumc+=a[i].t; if(C>1e7) i+=N; //防止n*C超过ll } cout<<ans<<endl; return 0; }
各种奇奇怪怪的数组...然后贪心策略、dp方程都比较难想到...但并不太难...估分:50?
T2:【p3746】组合数问题
- 求 (C nk,r + C nk,k+r + C nk,2k+r + ⋯ + C nk,(n−1)k+r + C nk,nk+r ) mod p。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3746】组合数问题 求 (C nk,r + C nk,k+r + C nk,2k+r + ⋯ + C nk,(n−1)k+r + C nk,nk+r ) mod p */ //【标签】组合数概念分析 + DP + 矩阵乘法优化 /* 1. 30pts,打表求出1000以内的组合数,枚举; 2. 5pts,k=1,直接求 ∑(i=r~n)Cn,i = 2^n-∑(i=0~r-1)Cn,i ,r<=50; 3. 5pts,k=2,二项式定理奇数项和为2^(n-1) 直接求<r的奇数项&偶数项的Cn,i之和 ,r<=50; */ /*【分析】考虑组合数C(n,m)的实际意义:从n个元素里选出m个元素的方案数。 那么本题就是求从n*k个元素里,选出 R (R%k=r) 个元素的方案数。 用 dp[i][j] 表示从前i个元素里,选出 J (J%k=j) 个元素的方案数。 第i个元素不选:dp[i][j]+=dp[i-1][j];第i个元素选:dp[i][j]+=dp[i-1][(j-1+k)%k]。*/ // 看来我已经完全忘记DP了......现在重中之重就是学习 矩阵乘法 啊啊啊啊啊!!! // 图见:https://images2018.cnblogs.com/blog/1056834/201803/1056834-20180307202812273-224717212.png void reads(ll &x){ //读入优化(正负整数) ll fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const ll N=100019; ll n,p,k,r,C[N],sum_[N],sum1[N]; ll ksm(ll a,ll b){ll anss=1;while(b>0) {if(b&1)anss=anss*a%p;a=a*a%p,b>>=1;}return anss;} int a[59][59],ans[59][59],f[59][59],CC[59][59]; //a:dp矩阵 void mul(int A[59][59],int B[59][59]){ memset(CC,0,sizeof(CC)); for(int i=0;i<k;++i) for(int j=0;j<k;++j) for(int kk=0;kk<k;++kk) CC[i][j]=(CC[i][j]+1LL*A[i][kk]*B[kk][j]%p)%p; for(int i=0;i<k;++i) for(int j=0;j<k;++j) A[i][j]=CC[i][j]; } int main(){ reads(n),reads(p),reads(k),reads(r); //jc[0]=1; for(ll i=1;i<=n;i++) jc[i]=jc[i-1]*i%p; //C n,0 = 1,C n,i = (n-i+1/i)*C n,i-1 C[0]=sum_[0]=sum1[0]=1; for(ll i=1;i<=min((ll)50,n);i++){ // 求 C n,i C[i]=C[i-1]*(n-i+1)*ksm(i,p-2),sum_[i]=(sum_[i-1]+C[i])%p; if((i%2==r%2)) sum1[i]=(sum1[i-1]+C[i])%p; else sum1[i]=sum1[i-1]; } if(k==1){ cout<<(ksm(2,n)-sum_[r-1]+p)%p<<endl; return 0; } if(k==2){ cout<<(ksm(2,2*n-1)-sum1[r-2]+p)%p<<endl; return 0; } //注意是2*n C[0]=1; for(ll i=1;i<=min((ll)1000,n*k);i++) // 求 C n*k,i C[i]=C[i-1]*(n*k-i+1)%p*ksm(i,p-2)%p; if(n<=30){ ll ans=0; for(ll i=0;i<=n;i++) ans=(ans+C[r+i*k])%p; cout<<ans<<endl; return 0; } for(int i=0;i<=k-2;i++) a[i][i]=a[i][i+1]=1; a[k-1][k-1]++,a[k-1][0]++; f[0][0]=1; for(int i=0;i<k;i++) ans[i][i]=1; ll m=1LL*n*k; // 矩阵ans = 单位矩阵e * (dp矩阵a)^m; for(;m;mul(a,a),m>>=1) if(m&1) mul(ans,a); mul(f,ans); //矩阵快速幂 printf("%d",f[0][r]); return 0; //我真的不会矩阵乘法啊啊啊啊... }
这题比较有意思,但思路的转化有点困难...然后想出来dp之后还要用矩阵乘法...(知识盲区啊啊啊)估分:0~60?
T3:【p3747】相逢是问候
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3747】相逢是问候 */ //【标签】欧拉函数 + 线段树 + 极限标记思想 void reads(ll &x){ //读入优化(正负整数) ll fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const ll N=50019,max_phi=15019; ll n,m,p,c,a[N],primes[N],p_cnt,phi[59],PHI_; bool vis_[N],f; ll ksm(ll a,ll b,ll p){ ll anss=1; while(b>0){ if(b&1) anss=anss*a; a=a*a,b>>=1; if(a>=p) f=1,a%=p; if(anss>=p) f=1,anss%=p; } return anss; } ll C(ll a,ll x){ ll tmp=a; if(tmp>phi[x]) tmp=tmp%phi[x]+phi[x]; for(ll i=x;i>0;i--){ f=0,tmp=ksm(c,tmp,phi[i-1]); if(f==1) tmp+=phi[i-1],f=0; } return tmp; } //-------------线段树---------------// struct tree{ ll l,r,tag,sum; }seg[N<<2]; void update(ll rt){ //tag:维护每个区间整体(最少)被修改了多少次 seg[rt].sum=seg[rt<<1].sum+seg[rt<<1|1].sum; seg[rt].tag=min(seg[rt<<1].tag,seg[rt<<1|1].tag); } void build(ll rt,ll l,ll r){ seg[rt].l=l,seg[rt].r=r; if(l==r){ seg[rt].sum=a[l]; return; } ll mid=(l+r)>>1; build(rt<<1,l,mid),build(rt<<1|1,mid+1,r),update(rt); } void change(ll rt,ll L,ll R){ //每次直接暴力修改,记区间最小标记次数tag,>=PHI_就不改了 if(seg[rt].tag>=PHI_) return; ll l=seg[rt].l,r=seg[rt].r; if(l==r){ seg[rt].sum=C(a[l],++seg[rt].tag)%p; return; } ll mid=(l+r)>>1; if(L<=mid) change(rt<<1,L,R); if(R>mid) change(rt<<1|1,L,R); update(rt); } //单点修改 ll query(ll rt,ll L,ll R){ ll l=seg[rt].l,r=seg[rt].r; if(r<L||l>R) return 0; if(L<=l&&R>=r) return seg[rt].sum%p; return (query(rt<<1,L,R)+query(rt<<1|1,L,R))%p; } //--------------欧拉部分---------------// ll Phi(ll x){ ll ans=x; //求欧拉函数 for(ll i=1;i<=p_cnt&&primes[i]*primes[i]<=x;i++){ if(!(x%primes[i])) ans=ans/primes[i]*(primes[i]-1); while(!(x%primes[i])) x/=primes[i]; } if(x>1) ans=ans/x*(x-1); return ans; } void get_prime(){ //欧拉筛素数 for(ll i=2;i<max_phi;i++){ if(!vis_[i]) primes[++p_cnt]=i; for(ll j=1;j<=p_cnt;j++) { if(i*primes[j]>max_phi) break; vis_[i*primes[j]]=1; if(i%primes[j]==0) break; } } phi[PHI_]=p; while(phi[PHI_]!=1) phi[++PHI_]=Phi(phi[PHI_-1]); phi[++PHI_]=1; } //---------------主程序-----------------// int main(){ reads(n),reads(m),reads(p),reads(c); for(ll i=1;i<=n;i++) reads(a[i]); get_prime(); build(1,1,n); for(ll i=1,op,l,r;i<=m;i++){ reads(op),reads(l),reads(r); if(op==0) change(1,l,r); //区间替换为c^ai if(op==1) printf("%lld\n",query(1,l,r)%p); } }
#include<bits/stdc++.h> #include<algorithm> #include<iostream> #include<cstdlib> #include<iomanip> #include<cstring> #include<complex> #include<vector> #include<cstdio> #include<string> #include<bitset> #include<ctime> #include<cmath> #include<queue> #include<stack> #include<map> #include<set> #define Cpy(x,y) memcpy(x,y,sizeof(x)) #define Set(x,y) memset(x,y,sizeof(x)) #define FILE "4869" #define mp make_pair #define pb push_back #define RG register #define il inline using namespace std; typedef unsigned long long ull; typedef vector<int>VI; typedef long long ll; typedef double dd; const int N=50010; const int M=1000010; const dd eps=1e-5; const int inf=2147483647; const ll INF=1ll<<60; const ll P=100000; il ll read(){ RG ll data=0,w=1;RG char ch=getchar(); while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar(); return data*w; } il void file(){ srand(time(NULL)+rand()); freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); } int n,m,p,c,cal[N],top,a[N]; il int getphi(int x){ RG int phi=x; for(RG int i=2;1ll*i*i<=x;i++) if(x%i==0){phi=phi/i*(i-1);while(x%i==0)x/=i;} if(x!=1)phi=phi/x*(x-1);return phi; } il int poww(int a,int b,int mod){ RG int ret=1;bool pda=0,pdret=0; while(b){ if(b&1){ if(pda||1ll*ret*a>=mod)pdret=1; ret=1ll*ret*a%mod; } b>>=1; if(1ll*a*a>=mod)pda=1; a=1ll*a*a%mod; } return ret+pdret*mod; } int listc[10005][52],listt[10005][52]; int sum[N<<2],cover[N<<2]; #define ls (i<<1) #define rs (i<<1|1) #define mid ((l+r)>>1) il void update(int i){ sum[i]=(sum[ls]+sum[rs])%p; cover[i]=min(cover[ls],cover[rs]); } il void build(int i,int l,int r){ if(l==r){a[l]=sum[i]=read();cover[i]=0;return;} build(ls,l,mid);build(rs,mid+1,r);update(i); } int query(int i,int l,int r,int x,int y){ if(x<=l&&r<=y)return sum[i];RG int s=0; if(x<=mid)s=(s+query(ls,l,mid,x,y))%p; if(mid<y)s=(s+query(rs,mid+1,r,x,y))%p; return s; } void modify(int i,int l,int r,int x,int y){ if(cover[i]==top-1)return; if(l==r){ cover[i]++; sum[i]=a[l]%cal[cover[i]+1]+(a[l]>=cal[cover[i]+1])*cal[cover[i]+1]; for(RG int t=cover[i];t;t--){ if(listc[sum[i]%10000][t]>=cal[t]||listt[sum[i]/10000][t]>=cal[t]) sum[i]=1ll*listc[sum[i]%10000][t]*listt[sum[i]/10000][t]%cal[t]+cal[t]; else sum[i]=1ll*listc[sum[i]%10000][t]*listt[sum[i]/10000][t]%cal[t]; } sum[i]%=p; return; } if(x<=mid)modify(ls,l,mid,x,y); if(mid<y)modify(rs,mid+1,r,x,y); update(i); } int main() { n=read();m=read();p=read();c=read(); cal[++top]=p; while(cal[top]!=1){ RG int x=getphi(cal[top]); cal[++top]=x; } cal[++top]=1; for(RG int i=1,ret;i<=top;i++){ for(RG int j=0;j<10000;j++) listc[j][i]=poww(c,j,cal[i]); ret=poww(c,10000,cal[i]); for(RG int j=0;j<10000;j++) listt[j][i]=poww(ret,j,cal[i]); } build(1,1,n); for(RG int i=1,opt,l,r;i<=m;i++){ opt=read();l=read();r=read(); if(!opt)modify(1,1,n,l,r); else printf("%d\n",query(1,1,n,l,r)); } return 0; }
毒瘤题...写了欧拉+线段树维护(这个线段树可能有点伪...)撑死了40分...╮(╯_╰)╭ 估分:呵呵呵呵呵呵...
T4:【p3748】摧毁“树形图”
- 无向树,选两条边不重复的路径,删掉链上所有点以及所有相连的边,
- 使得剩下的连通块数目最多,求剩下的最多连通块个数。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3748】摧毁树形图 无向树,选两条边不重复的路径,删掉链上所有点以及所有相连的边, 使得剩下的连通块数目最多,求连通块个数。 */ //【标签】树形DP /* f[x][0]:穿过x向上的半条链 f[x][1]:不穿过x且完全在子树内的一条链 f[x][2]:穿过x且完全在子树内的一条链 f[x][3]:穿过x向上的半条链以及完全在子树内的一条链 */ void reads(ll &x){ //读入优化(正负整数) ll fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const ll N=500019; vector<ll> e[N]; ll f[N][4],ans; void dfs(const ll &x,const ll &fa){ const bool isrt=(x==1); const ll ver_tot=e[x].size()-!isrt; f[x][0]=f[x][2]=f[x][3]=ver_tot; f[x][1]=1; ll maxx=0; for(ll i=0;i<(ll)e[x].size();i++) { ll &y=e[x][i]; if(y==fa) continue; dfs(y,x); ans=max(ans,max(f[x][0]+f[y][3]-isrt,f[x][3]+f[y][0]-isrt)); ans=max(ans,f[x][1]+f[y][1]-1); ans=max(ans,f[x][1]+f[y][2]); ans=max(ans,f[x][2]+f[y][1]-isrt); ans=max(ans,f[x][2]+f[y][2]-isrt); f[x][1]=max(f[x][1],max(f[y][1],f[y][2]+1)); f[x][3]=max(f[x][3],max(f[y][3]+ver_tot-1,f[y][0]+maxx+ver_tot-2)); f[x][3]=max(max(f[x][3],f[x][2]+f[y][0]-1), max(f[x][0]+f[y][1]-1,f[x][0]+f[y][2]-1)); f[x][2]=max(f[x][2],f[x][0]+f[y][0]-1); f[x][0]=max(f[x][0],f[y][0]+ver_tot-1); f[x][2]=max(f[x][2],f[x][0]); f[x][3]=max(f[x][3],f[x][2]); maxx=max(maxx,max(f[y][1],f[y][2])); } } int main(){ ll T,x,n; reads(T),reads(x); for(ll i=0;i<T;i++){ reads(n); for(ll i=0,u;i<x*2;i++) reads(u); for(ll i=1,u,v;i<n;i++) reads(u),reads(v),e[u].push_back(v),e[v].push_back(u); ans=0; dfs(1,0); printf("%lld\n",ans); for(ll i=1;i<=n;i++) e[i].clear(); } }
网上这题的题解一般都很玄学(居然没有人认真做一做吗...)树形dp的话,估计是很难AC了...(委婉的表达我会爆零)
T5:【p3749】寿司餐厅
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3749】寿司餐厅 */ //【标签】网络流 + 最大权闭合子图 + 读题/细节处理 /* 1. n<=2,种数只有 {不选}{1}{2}{1,2}{1;2} 10pts。 2. 60%数据m=0,即支出=∑每个代号吃了的种类数*代号。 */ /*【分析】感觉有点像网络流但我不会写0.0 最大权闭合子图。 日常%GXZ https://www.cnblogs.com/GXZlegend/p/6795784.html 对于每个点(i,j)(j>i),如果它被选择,那么点(i,j-1)和点(i+1,j)也一定被选择。 由此建点权图。对于点(i,j)(j>i),点权为d[i][j],并向点(i,j-1)和点(i+1,j)连边。 对于点(i,i),点权为d[i][i]-a[i](收益减去费用),并向编号a[i]连边。 对于编号p,点权为-m*p*p。所求为最大权闭合图,所以转化为网络流最小割来求。 最后的答案 : 闭合图最大权 = 正权和sum - 最大流flow。*/ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=50019,inf=0x3f3f3f3f; int a[N],d[110][110],id[110][110],cnt_; int s,t,tot=-1,n1,n2,n3,head[N],dep[N]; //s为源点,t为汇点 struct node{ int nextt,ver,w; }e[N]; void add(int x,int y,int z) //正向边权值为1,反向边权值为0 { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; e[++tot].ver=x,e[tot].nextt=head[y],e[tot].w=0,head[y]=tot; } int bfs(){ memset(dep,0,sizeof(dep)); //dep记录深度 queue<int> q; while(!q.empty()) q.pop(); dep[s]=1; q.push(s); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];i!=-1;i=e[i].nextt) if((e[i].w>0)&&(dep[e[i].ver]==0)) //分层 dep[e[i].ver]=dep[u]+1,q.push(e[i].ver); } if(dep[t]!=0) return 1; else return 0; //此时不存在分层图也不存在增广路 } int dfs(int u,int lastt){ int ans=0; if(u==t) return lastt; //lastt:此点还剩余的流量 for(int i=head[u];i!=-1&&ans<lastt;i=e[i].nextt) if((dep[e[i].ver]==dep[u]+1)&&(e[i].w!=0)){ int f=dfs(e[i].ver,min(lastt-ans,e[i].w)); if(f>0){ e[i].w-=f,e[i^1].w+=f,ans+=f; } } if(ans<lastt) dep[u]=-1; return ans; } int main(){ int n,m,k=0,sum=0; reads(n),reads(m); memset(head,-1,sizeof(head)); //死也会忘记的... for(int i=1;i<=n;i++) reads(a[i]),k=max(k,a[i]); for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) reads(d[i][j]),id[i][j]=++cnt_; s=0,t=cnt_+k+1; //cnt_个d(i,j),k种编号 for(int i=1;i<=k;i++) add(cnt_+i,t,m*i*i); //↑↑k种编号向汇点连边(便于处理最后的m*x^2) 利用线性的‘互相影响’关系 for(int i=1;i<=n;i++) add(id[i][i],cnt_+a[i],inf), d[i][i]-=a[i]; //点权-a[i](费用),向编号点a[i]连边 for(int i=1;i<=n;i++) for(int j=i;j<=n;j++){ //最大全闭合子图的建边 if(d[i][j]>0) add(s,id[i][j],d[i][j]),sum+=d[i][j]; //正权 if(d[i][j]<0) add(id[i][j],t,-d[i][j]); //负权,向T建立全值为绝对值的边 if(j>i) add(id[i][j],id[i][j-1],inf),add(id[i][j],id[i+1][j],inf); } //↑↑处理相关联有影响的性质:(i,j) -> (i,j-1)、(i+1,j) while(bfs()) sum-=dfs(s,inf); printf("%d\n",sum); }
其实这题还是很巧妙的...但如果学了“最大权闭合子图”,这题难度应该不大...应该 95~100 吧?
T6:【p3750】分手是祝愿
- n个开关,操作i则所有i的约数的开关状态^1,等概率操作。
- 若在某种状态下,在k次之内可以全灭,则选择最小操作次数。
- 求所有操作情况下,全灭的操作次数的期望*n!%100003。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3750】分手是祝愿 n个开关,操作i则所有i的约数的开关状态^1,等概率操作。 若在某种状态下,在k次之内可以全灭,则选择最小操作次数。 求所有操作情况下,全灭的操作次数的期望*n!%100003。*/ //【标签】期望DP + 贪心 + inv数组/因子预处理 /* 1. 50%数据k=n,即只需要找min的操作次数:贪心,从大到小有亮的灭掉。 (结果我只有45pts...)2. 优化了找因子的方式,1LL*全部写完整,可以有80pts。 */ /*【分析】开关问题:操作两次=没操作。 用dp[i]表示对于n盏灯,从‘需要按i次能全部熄灭’到‘按i−1次能全部熄灭’的期望按灯次数。*/ /* 考虑这一轮(从上一状态到这一个状态,需要按灯的期望次数值)的情况: 1.有i/n的概率按到需要按的灯,此时的期望为(i/n)*1 (只需按一次,就可以达到新状态)。 2.有(n−i)/n的概率按到不需要按的灯,此时的期望为: (n−i)/n∗( (dp[i+1]+1)(把这个按错了的按回去,重新到达状态i+1) + dp[i](重新按这一轮) ) 那么:dp[i]=i/n+(n−i)/n∗(dp[i]+dp[i+1]+1); 化简一下:dp[i]=n/i+(n−i)*dp[i+1]/i。*/ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=100019,p=100003; int k,n,minn=0,oo[N],ans=0,f[N],inv[N]; vector <int> g[N]; int main(){ reads(n),reads(k); for(int i=1;i<=n;i++) reads(oo[i]); int jc_=1; for(int i=1;i<=n;i++) jc_=1LL*jc_*i%p; inv[1]=1; for(int i=2;i<=n;i++) inv[i]=1LL*(p-p/i)*inv[p%i]%p; for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=i) g[j].push_back(i); for(int i=n;i>=1;i--) if(oo[i]==1) { minn++; for(int j=0;j<(int)g[i].size();j++) oo[g[i][j]]^=1; } if(minn<=k){ cout<<1LL*minn*jc_%p<<endl; return 0; } //这里不写 1LL* 会掉5分 f[n]=1; for(int i=n-1;i>=1;i--) f[i]=1LL*(n+1LL*(n-i)*f[i+1]%p)*inv[i]%p; for(int i=minn;i>k;i--) ans=(ans+f[i])%p; ans=(ans+k)%p; cout<<1LL*ans*jc_%p<<endl; return 0; }
期望dp的技能点为0...甚至连要逆推都不知道...期望==看着题目发呆...(愁啊...
——时间划过风的轨迹,那个少年,还在等你