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 */
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 */
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 + (n−i)/ 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 */
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 */