【浮*光】#省选真题# [六省联考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;
}
【p3745】期末考试 //【标签】思维题 + 分析结论 + dp

 

各种奇奇怪怪的数组...然后贪心策略、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; //我真的不会矩阵乘法啊啊啊啊...
}
【p3746】组合数问题 //【标签】组合数概念分析 + DP + 矩阵乘法优化

 

这题比较有意思,但思路的转化有点困难...然后想出来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);
    }
}
【p3747】相逢是问候 //【标签】欧拉函数 + 线段树 + 极限标记思想

 

#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;
}
【p3747】相逢是问候 // AC正解(hss的rp值过于低...所以这个↑↑只有10分)

 

毒瘤题...写了欧拉+线段树维护(这个线段树可能有点伪...)撑死了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();
    }
}
【p3748】摧毁树形图 //【标签】较复杂的树形dp

 

网上这题的题解一般都很玄学(居然没有人认真做一做吗...)树形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);

}
【p3749】寿司餐厅 //【标签】网络流 + 最大权闭合子图 + 读题/细节处理

 

其实这题还是很巧妙的...但如果学了“最大权闭合子图”,这题难度应该不大...应该 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;

}
【p3750】分手是祝愿 //【标签】期望DP + 贪心 + inv数组/因子预处理

 

期望dp的技能点为0...甚至连要逆推都不知道...期望==看着题目发呆...(愁啊...

 


 

 

 

                                                       ——时间划过风的轨迹,那个少年,还在等你

 

posted @ 2019-03-13 20:18  花神&缘浅flora  阅读(205)  评论(0编辑  收藏  举报