开学考试题5:2017黑龙江省选

Day1

T1:P3745 [六省联考2017]期末考试

隐藏的水题。。。

分析:

一看题,难,再看数据范围,连dfs爆搜的分都没有。但其中很多特殊点暗示了做法:

1. A B大,C小,说明只能让学生不愉快,不能调课。

2.C大,A B小,说明只能用AB两种方式调课:那么我们为了不让学生产生不愉快度,就要满足b[i]中最大的<=t[i]中最小的(记为minn)。

若B>A,直接把超时的科目都提前,统计代价。

若A<=B,优选A方式,可是A方式要满足将天数提前的同时,也要将部分科目天数延后,所以考虑计算<minn的b值可以延后的总天数是多少,>minn值需要提前的总天数是多少,

然后比较:够了就都用A,不够的部分用B的代价来补。

3.……

2.的做法暗示了当已知最晚科目时间下的做法,可是我们并不知道最晚科目的时间。怎么办?     ——枚举就好了。

按这种思路,可以产生一个n^2的做法了。

考虑优化:

1. 枚举一个最晚时间,统计使科目调整到那个时间的最小花费,以及学生产生的不愉快度时,我们是O(n)去做的,将t,b排序,用两个前缀和数组,维护一下,统计答案时直接二分找到不满足的位置,然后用前缀和O(1)统计即可,时间复杂度:O(n*logn)

2.发现答案是满足先递减再递增的性质的,可以用三分。O(n*logn)

3.初始化几个数组(真的不怎么懂他们是怎么做的),然后各种搞。。 O(n)。

我打的是第一种。

总结:

通过特殊点推测正解,发现问题的本质。

代码细节要注意,该开long long就要开,register int是int类型!!

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ri register int
#define N 100005
const ll inf=1ll<<62;
ll t[N],b[N],A,B,C,n,m,maxx=0,minn=inf,sumt[N],sumb[N];
ll read()
{
    ll x=0; int fl=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*fl;
}
void work1()
{
    ll maxn=0,ans=0;
    for(ri i=1;i<=m;++i) maxn=max(maxn,b[i]);
    for(ri i=1;i<=n;++i) if(t[i]<maxn) ans+=(maxn-t[i])*C;
    printf("%lld
",ans);
}
ll query1(ll x)//zeng B
{
    ll pos=lower_bound(b+1,b+m+1,x)-b;
    ll tmp=( sumb[m]-sumb[pos-1] - x*(m-pos+1) )*B;
    return tmp;
}
ll query2(ll x)//change A
{
    ll sum1=0,sum2=0;
    ll pos=lower_bound(b+1,b+1+m,x)-b;
    sum1=x*(pos-1) - sumb[pos-1]; 
    sum2=sumb[m]-sumb[pos-1] - x*(m-pos+1);
    if(sum1>=sum2) return sum2*A;
    else return sum1*A+(sum2-sum1)*B;
}
void work2()
{
    ll ans=inf;
    for(ri i=1;i<=maxx;++i){
        ll tmp=0;
        if(A>=B) tmp=query1(i);
        else tmp=query2(i);
        ll pos=lower_bound(t+1,t+1+n,i)-t;
        tmp+=( (ll)i*(pos-1) - sumt[pos-1] )*C;
        if(tmp>=0) ans=min(ans,tmp);
    }
    printf("%lld
",ans);
}
void work3()
{
    ll ans=0;
    if(A>=B) ans=query1(minn);
    else ans=query2(minn);
    printf("%lld
",ans);
}
int main()
{
    freopen("exam.in","r",stdin);
    freopen("exam.out","w",stdout);
    A=read(); B=read(); C=read();
    n=read(); m=read();
    for(ri i=1;i<=n;++i) t[i]=read(),maxx=max(maxx,t[i]),minn=min(minn,t[i]);
    for(ri i=1;i<=m;++i) b[i]=read(),maxx=max(maxx,b[i]);
    sort(t+1,t+1+n); sort(b+1,b+1+m);
    for(ri i=1;i<=n;++i) sumt[i]=sumt[i-1]+t[i];
    for(ri i=1;i<=m;++i) sumb[i]=sumb[i-1]+b[i];
    if(A==B && B==1e9 && C<A) work1();
    else if(C==1e16) work3();
    else work2();    
}
/*
3 5 4
5 6
1 1 4 7 8
2 3 3 1 8 2

1000 1000 10000000000000000
2 2
1 3
2 3

149 896 130
2 5
1 4
2 6 8 8 9
*/
View Code

T2:P3747 [六省联考2017]相逢是问候

首先要明确一点:指数不能随便取模!!

可以先做一下这道题:P4139 上帝与集合的正确用法

所以欧拉定理就派上用场了:欧拉定理推论(其实是别人写的特别好的题解)

那么怎么求一个数的欧拉值呢?

1.利用欧拉函数是积性函数的性质,线性筛递推求:O(n)

void init_phi()
{
    for(ri i=2;i<=n;++i){
        if(!pri[i]) su[++cnt]=i,phi[i]=i-1;
        for(ri j=1;j<=cnt&&su[j]*i<=n;++j){
            pri[su[j]*i]=1;
            if(i%su[j]==0){
                phi[su[j]*i]=su[j]*phi[i];
                break;
            }
            else phi[su[j]*i]=phi[i]*(su[j]-1);
        }
    }
}
线性筛

2.直接质因数分解求单个欧拉值:(注意特判素数)复杂度O(logn)

ll calc_phi(ll x)
{
    ll tmp=x;
    for(ri i=2;i<=sqrt(x);++i)
    if(x%i==0){
        tmp=tmp/i*(i-1);//利用公式求欧拉函数 
        while(x%i==0) x/=i;
    }
    if(x>1) tmp=tmp/x*(x-1);//处理质数情况 
    return tmp;
}
质因数分解求欧拉

然后区间修改时,用线段树暴力递归到叶子节点去修改每一个值,怎么保证复杂度呢:每个数最多只会被修改log次,用一个tim数组记录被修改了多少次,超过一定次数后就返回。

修改的时候利用上述公式递归求应该修改的值。

但是时间复杂度太大了,就要初始化c的次幂,省去log c的复杂度。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ri register int
#define mid ((l+r)>>1)
#define N 50005
ll sum[N*4],tim[N*4],c,a[N],phi[N],pow1[10005][30],pow2[10005][30],p,fl=0,cnt=0;
bool b1[10005][30],b2[10005][30];
int n,m;
int read()
{
    int x=0,fl=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*fl;
}
ll calc_phi(ll x)
{
    ll tmp=x;
    for(ri i=2;i<=sqrt(x);++i)
    if(x%i==0){
        tmp=tmp/i*(i-1);//利用公式求欧拉函数 
        while(x%i==0) x/=i;
    }
    if(x>1) tmp=tmp/x*(x-1);//处理质数情况 
    return tmp;
}
void init()
{
    ll tmp=p; phi[0]=p;//第0层的模数为它自己 
    while(tmp!=1) tmp=calc_phi(tmp),phi[++cnt]=tmp;//O(log p)求出p递归的欧拉值 
    phi[++cnt]=1; 
    for(ri i=0;i<=cnt;++i){
        pow1[0][i]=1;//pow1是c的 j次方   i是递归的层数 
        for(ri j=1;j<=10000;++j){
            pow1[j][i]=pow1[j-1][i]*c;
            if(pow1[j][i]>=phi[i]) pow1[j][i]%=phi[i],b1[j][i]=1;//b1标记处理指数大于phi[i]的情况 
            b1[j][i]|=b1[j-1][i];
        }
    }
    for(ri i=0;i<=cnt;++i){
        pow2[0][i]=1;//pow2是c的 j*10000 次方 
        b2[1][i]=b1[10000][i];
        for(ri j=1;j<=10000;++j){
            pow2[j][i]=pow2[j-1][i]*pow1[10000][i];
            if(pow2[j][i]>=phi[i]) pow2[j][i]%=phi[i],b2[j][i]=1;
            b2[j][i]|=b2[j-1][i]|b1[10000][i];//
        }
    }
}
ll calc(ll x,ll dep)//在第dep层时c的x次方 
{
    fl=0;//便于dfs里面的标记判断 
    //以10000作为标准预处理出了c^i与c^(j*10000)的值 避免使用快速幂超时 
    ll x1=x/10000,x2=x%10000,tmp=pow2[x1][dep]*pow1[x2][dep];
    if(tmp>=phi[dep]) tmp%=phi[dep],fl=1;//!!phi[dep]
    fl|=b2[x1][dep]|b1[x2][dep];
    return tmp;
}
ll dfs(ll x,ll dep,ll lim)
{
    fl=0;//打标记判断指数是否大于phi[dep] 如果大于的话,要+phi[dep]
    if(dep==lim){//递归到了期望的次方位置 就返回求出的值 
        if(x>=phi[dep]) fl=1,x%=phi[dep];
        return x;
    }
    ll xx=dfs(x,dep+1,lim);//递归 
    return calc( fl? xx+phi[dep+1] : xx ,dep);//计算那一长串的次方 如果有标记 就要加上 上一次的phi值 
}
void update(int s) { sum[s]=sum[s<<1]+sum[s<<1|1]; if(sum[s]>=p) sum[s]-=p; tim[s]=min(tim[s<<1],tim[s<<1|1]); }
void build(int s,int l,int r)
{
    if(l==r){ sum[s]=a[l]; return ; }
    build(s<<1,l,mid); build(s<<1|1,mid+1,r);
    update(s);
}
void modify(int s,int l,int r,int L,int R)
//区间修改转换成单点暴力修改,但维护了修改次数,大于log次就没有必要修改了,类似于取mod的线段树一样 
{ 
    if(tim[s]>=cnt) return ;
    if(l==r) {  tim[s]++; sum[s]=dfs(a[l],0,tim[s]); return ; }//通过递归来找到第x次c的次方时,数学公式推出来的那个值 
    if(L<=mid && tim[s<<1]<cnt) modify(s<<1,l,mid,L,R);
    if(R>mid && tim[s<<1|1]<cnt) modify(s<<1|1,mid+1,r,L,R);
    update(s);
}
ll query(int s,int l,int r,int L,int R)
{
    if(L<=l && r<=R) return sum[s];
    ll ans=0;
    if(L<=mid) ans=ans+query(s<<1,l,mid,L,R);
    if(R>mid)  ans=ans+query(s<<1|1,mid+1,r,L,R);
    if(ans>=p) ans-=p;//防止%多了超时 可是不会没减够吗 
    return ans;
}
int main()
{
    freopen("verbinden.in","r",stdin);
    freopen("verbinden.out","w",stdout);
    n=read(); m=read(); p=read(); c=read();
    for(ri i=1;i<=n;++i) a[i]=read();
    init();
    build(1,1,n);
    while(m--){
        int op=read(),l=read(),r=read();
        if(op==0) modify(1,1,n,l,r);
        else printf("%lld
",query(1,1,n,l,r));
    }/**/
}
/*
4 4 7 2
1 2 3 4
0 1 4
1 2 4
0 1 4
1 1 3
*/
View Code

 

Day2

T2:

首先我们可以发现:最优策略是从后往前倒着选,如果是开的,把它和它的约数都操作一遍,按键次数cnt++。

为什么是对的?

一个位置只会被它后面那个位置所影响,所以说如果一个位置必须被操作,那么枚举到它了,它就必须按一次(因为我们是倒序枚举的),前面的按了不可能使它改变。

如果cnt<=k,说明每一步都要按照最优策略来做。直接输出cnt*(n!)即可

否则,就要求大于cnt部分,按照随机策略的按到cnt的期望步数。

定义f [ i ]为需要按 i 个键 变成 需要按 i-1 个键 的期望次数。

从一开始的乱选,到最后只剩 k 步然后去选最优策略的答案为:f[cnt]+f[cnt−1]+....+f[k+1] (因为f是两步的期望差)。

转移:f [ i ] = i / n + (ni)/ n  × (f [ i ] + f [ i+1 ] + 1)

有 i / n 的概率按到正确的键,剩下的概率按到正确的键。

如果是正确的键,就直接转移到下一个状态。  否则,就会需要到 i+1 这一个状态再转移回来,然后还应该再从这个状态转移到下一次,按了这个键又耗费了一步,所以要+1。

#include<bits/stdc++.h>
using namespace std;
#define ri register int
#define N 100005
#define mod 100003
#define ll long long
ll a[N],f[N],n,k,cnt,ans=0;
ll read()
{
    ll x=0,fl=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); }
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
    return fl*x;
}
ll quick_pow(ll a,ll k)
{
    ll ans=1;
    while(k) { if(k&1) ans=ans*a%mod; a=a*a%mod; k>>=1; }
    return ans;
}
void work()
{
    f[n+1]=0;
    for(ll i=n;i>=1;--i)
    f[i]= ( (n-i)*f[i+1] %mod + n ) %mod *quick_pow(i,mod-2) %mod ;
    for(ll i=k+1;i<=cnt;++i) ans=(ans+f[i])%mod;
    ans=(ans+k)%mod;
}
int main()
{
    n=read(); k=read();
    for(ll i=1;i<=n;++i) a[i]=read();
    for(ll i=n;i>=1;--i){
        if(a[i]){
            cnt++;
            for(ll j=1;j*j<=i;++j)
            if(i%j==0) {
                a[j]^=1;
                if(j*j!=i) a[i/j]^=1;
            }
        }
    }
    if(cnt<=k) ans=cnt;
    else work();
    for(ll i=1;i<=n;++i) ans=ans*i%mod;
    printf("%lld
",ans);
    return 0;
}
/*
4 0
0 0 1 1
*/
View Code

T3:[六省联考2017]寿司餐厅

最大权闭合子图问题。

首先分析题:限制那么多,数据范围也很小,考虑网络流。

如果不考虑花费,怎样才能得到尽量多的美味度呢?

关键在于如何处理区间关联性:对于每一个(i,j)的区间,向比它小1的区间连边:(i+1,j)和(i,j+1)。在根据它权值的正负决定向s连边还是向t连边。(正连s,负连t

然后求最小割(也就是最大流),用所有正权值的和 - 最小割,即最大的美味度。为什么是所有正权值的和?

现在考虑加入花费:m*x^2+c*x

对于 m*x^2 的部分,只需要对寿司的代号新建一个点连向 t即可

而 c 代表选这种代号的次数,我们怎么知道这种代号被选多少次呢?

选一份寿司必定选了相应的代号,只需要把寿司向 t 连 x 边权的边即可。

#include<bits/stdc++.h>
using namespace std;
#define ri register int
#define nn 1005
#define N 1000005
int tot=1,head[N],to[N<<1],nex[N<<1],w[N<<1],lev[N],cnt=0,cur[N];
int s,t,inf=1<<30,c[nn],idd[nn],id[nn][nn],d[nn][nn];
int read()
{
    int x=0,fl=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); }
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
    return fl*x;
}
void add(int a,int b,int ww)
{
    to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww;
    to[++tot]=a; nex[tot]=head[b]; head[b]=tot; w[tot]=0;
}
queue<int> q;
bool bfs()
{
    memset(lev,-1,sizeof(lev));
    lev[s]=1; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(ri i=head[u];i!=-1;i=nex[i]){
            int v=to[i];
            if(w[i]>0 && lev[v]==-1) lev[v]=lev[u]+1,q.push(v);
        }
    }
    return lev[t]!=-1;
}
int dfs(int u,int flow)
{
    if(u==t) return flow;
    int ret=flow;
    for(ri i=cur[u];i!=-1;i=nex[i]){
        cur[u]=i;
        int v=to[i];
        if(ret<=0) break;
        if(lev[v]==lev[u]+1 && w[i]>0){
            int k=dfs(v,min(w[i],ret));
            w[i]-=k; ret-=k; w[i^1]+=k;
        }
    }
    return flow-ret;
}
long long sum=0;
void dinic()
{
    long long ans=0;
    while(bfs()){
        memcpy(cur,head,sizeof(head));
        ans+=dfs(s,inf);
    }
    printf("%lld
",sum-ans);
}
int main()
{
    int n,m;
    memset(head,-1,sizeof(head));
    n=read(); m=read();
    s=0,t=N-5;
    for(ri i=1;i<=n;++i){
        c[i]=read();
        if(!idd[c[i]]) idd[c[i]]=++cnt,add(idd[c[i]],t,m*c[i]*c[i]);
    } 
    for(ri i=1;i<=n;++i)
     for(ri j=i;j<=n;++j)
      d[i][j]=read(),id[i][j]=++cnt,sum+= d[i][j]>0?d[i][j]:0 ;//注意这里应该是所有的正权值 
    for(ri i=1;i<=n;++i){
        add(id[i][i],idd[c[i]],inf);
        add(id[i][i],t,c[i]);
    } 
    for(ri i=1;i<=n;++i)
     for(ri j=i;j<=n;++j){
         if(d[i][j]>0) add(s,id[i][j],d[i][j]);
         else add(id[i][j],t,-d[i][j]);
         if(id[i+1][j]) add(id[i][j],id[i+1][j],inf);
         if(id[i][j-1]) add(id[i][j],id[i][j-1],inf);
    }
    dinic();
}
/*
3 1
2 3 2
5 -10 15
-10 15
15
*/
View Code
原文地址:https://www.cnblogs.com/mowanying/p/11544067.html