期末考试
Luogu
LOJ
BZOJ
显然我们只需要考虑最后公布成绩的那一天。
枚举这一天,预处理前缀和之后即可(O(1))计算最小代价。
注意倒数第二个Subtask这最优的日期就是(min(b_i)),直接计算即可。(如果套用通法会爆long long
,但是开unsigned long long
就行了)
#include<cctype>
#include<cstdio>
#include<algorithm>
using i64=unsigned long long;
const int N=100007;const i64 inf=1e18;
i64 t1[N],t2[N],A,B,C;
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
i64 cal(i64 a,i64 b){return A<B? a>b? b*A+(a-b)*B:a*A:B*a;}
int main()
{
int n,m;i64 r1=0,r2=0,r3=0,s1=0,s2=0,s3=0,ans=inf;
A=read(),B=read(),scanf("%lld",&C),n=read(),m=read();
for(int i=1,x;i<=n;++i) ++t2[x=read()],s3+=x,++r3;
for(int i=1,x;i<=m;++i) ++t1[x=read()],s2+=x,++r2;
for(int i=100000;i;--i)
{
s1+=i*t1[i],r1+=t1[i],s2-=i*t1[i],r2-=t1[i],s3-=i*t2[i],r3-=t2[i];
if(!s1) continue;
ans=std::min(ans,cal(s1-i*r1,r2*i-s2)+(r3*i-s3)*C);
}
printf("%lld",ans);
}
相逢是问候
Luogu
LOJ
BZOJ
给定一个数(p),最多进行(O(log p))次(pleftarrowvarphi(p))即可令(p=1)。
也就是说一个位置在被修改了(O(log p))次之后,它的值就不会变了。
那么我们记录一个位置被修改过多少次,每次修改的时候区间内的所有位置单独拿出来暴力计算即可。
预处理快速幂即可(O(log^2p))进行单点计算。
为了不再访问那些被修改超过(O(log p))次之后的位置,我们可以使用线段树记录区间修改最小次数,或者利用并查集+树状数组。
时间复杂度为(O(nlog p(log p+log n)))。
#include<cctype>
#include<cstdio>
#include<vector>
#include<algorithm>
const int N=50007,M=10007;
int n,m,p,c,a[N],o[N],t[N],tim[N],fa[N],pw1[30][M],pw2[30][M];std::vector<int>mod;
void inc(int&a,int b){a+=b-p,a+=a>>31&p;}
int pow(int a,int b){return 1ll*pw1[b][a%10000]*pw2[b][a/10000]%mod[b];}
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
int phi(int x){int val=x;for(int p=2;p*p<=x;++p) if(!(x%p)) for(val-=val/p;!(x%p);x/=p);return val-(x>1)*val/x;}
int find(int x){return x==fa[x]? x:fa[x]=find(fa[x]);}
void add(int p,int v){for(;p<=n;p+=p&-p)inc(t[p],v);}
int ask(int p){int r=0;for(;p;p^=p&-p)inc(r,t[p]);return r;}
void modify(int p,int t){for(a[p]=o[p]>=mod[t]?o[p]%mod[t]+mod[t]:o[p];t--;)if(a[p]=pow(a[p],t),t&&!a[p])a[p]+=mod[t];}
void update(int l,int r){for(l=find(l);l<=r;l=find(l+1))if(add(l,p-a[l]),modify(l,++tim[l]),add(l,a[l]),tim[l]==(int)mod.size()-1)fa[l]=l+1;}
int main()
{
n=read(),m=read(),p=read(),c=read(),fa[n+1]=n+1;
for(int i=1;i<=n;++i) add(fa[i]=i,o[i]=a[i]=read());
for(int x=p;x^1;x=phi(x)) mod.push_back(x);
mod.push_back(1),mod.push_back(1);
for(int i=0;i<(int)mod.size();++i)
{
for(int j=pw1[i][0]=1;j<=10000;++j) pw1[i][j]=1ll*pw1[i][j-1]*c%mod[i];
for(int j=pw2[i][0]=1;j<=10000;++j) pw2[i][j]=1ll*pw2[i][j-1]*pw1[i][10000]%mod[i];
}
for(int o,l,r;m;--m) o=read(),l=read(),r=read(),o? printf("%d
",(ask(r)-ask(l-1)+p)%p):(update(l,r),0);
}
组合数问题
Luogu
LOJ
BZOJ
(sumlimits_{iequiv rpmod k}{nkchoose i}=[x^r](1+x)^{nk}mod(x^k-1))
循环卷积快速幂即可。
#include<cstdio>
#include<vector>
using vec=std::vector<int>;
int n,r,p,k;
void inc(int&a,int b){a+=b-p,a+=a>>31&p;}
int mul(int a,int b){return 1ll*a*b%p;}
vec operator*(const vec&f,const vec&g)
{
vec h(k);
for(int i=0;i<k;++i) for(int j=0;j<k;++j) inc(h[(i+j)%k],mul(f[i],g[j]));
return h;
}
int main()
{
scanf("%d%d%d%d",&n,&p,&k,&r);vec E(k),I(k);
I[0]=1,k==1? E[0]=2%p:E[0]=E[1]=1;
for(long long e=1ll*n*k;e;e>>=1,E=E*E) if(e&1) I=E*I;
printf("%d",I[r]);
}
摧毁树状图
Luogu
LOJ
BZOJ
很显然是一个树形dp。
(f_{u,0})表示(u)子树内过(u)的一条单链。
(f_{u,1})表示(u)子树内不经过(u)的一条路径。
(f_{u,2})表示(u)子树内过(u)的一条路径。
(f_{u,3})表示(u)子树内的一条路径和过(u)的一条单链。
具体的看这个吧Link。
或者写个暴力拍,拍出错就加一种情况。
考场上这种旷野大讨论的题一般都不会去做的。
#include<cctype>
#include<cstdio>
#include<vector>
#include<cstring>
const int N=100007;
int ans,f[N][4],deg[N];std::vector<int>e[N];char ibuf[1<<23|1],*iS=ibuf;
int read(){int x=0;while(isspace(*iS))++iS;while(isdigit(*iS))(x*=10)+=*iS++&15;return x;}
void max(int&a,int b){if(b>a)a=b;}
void dfs(int u,int fa)
{
f[u][0]=f[u][2]=f[u][3]=deg[u],f[u][1]=1;int mx=0;
for(int v:e[u])
{
if(v==fa) continue;
dfs(v,u);
max(ans,f[u][3]+f[v][0]-(u==1));
max(ans,f[u][0]+f[v][3]-(u==1));
max(ans,f[u][1]+f[v][2]);
max(ans,f[u][1]+f[v][1]-1);
max(ans,f[u][2]+f[v][1]-(u==1));
max(ans,f[u][2]+f[v][2]-(u==1));
max(f[u][1],f[v][1]);
max(f[u][1],f[v][2]+1);
max(f[u][3],f[u][0]+f[v][2]-1);
max(f[u][3],f[u][2]+f[v][0]-1);
max(f[u][3],f[v][3]+deg[u]-1);
max(f[u][3],f[v][0]+mx+deg[u]-2);
max(f[u][3],f[u][0]+f[v][1]-1);
max(f[u][2],f[u][0]+f[v][0]-1);
max(f[u][0],f[v][0]+deg[u]-1);
max(f[u][2],f[u][0]);
max(f[u][3],f[u][2]);
max(mx,f[v][1]);
max(mx,f[v][2]);
}
}
int main()
{
fread(ibuf,1,1<<23,stdin);
for(int T=read(),o=read(),n;T;--T)
{
n=read(),memset(deg+1,0,n<<2);
for(int i=1;i<=n;++i) e[i].clear();
for(int i=1;i<=o;++i) read(),read();
for(int i=2,u,v;i<=n;++i) ++deg[u=read()],++deg[v=read()],--deg[i],e[u].push_back(v),e[v].push_back(u);
dfs(1,ans=0),printf("%d
",ans);
}
}
分手是祝愿
Luogu
LOJ
BZOJ
显然最优的决策是倒序遍历,碰到开着的灯就关上。
然后我们可以枚举约数变枚举倍数在(O(nlog n))的时间复杂度内求出最小操作次数(cnt)。
设(f_i)表示从最小操作次数为(i)的状态转移到最小操作次数为(i-1)的期望操作次数。
转移方程很显然,(f_i=frac in+frac{n-i}n(f_i+f_{i+1}+1))即(f_i=frac{n+(n-i)f_{i+1}}i)。
最后答案就是(min(k,cnt)+sumlimits_{i=k+1}^{cnt}f_i)。
#include<cstdio>
#include<algorithm>
const int P=100003;
int a[P],f[P];
int read(){int x;scanf("%d",&x);return x;}
void inc(int&a,int b){a+=b-P,a+=a>>31&P;}
int mul(int a,int b){return 1ll*a*b%P;}
int pow(int a,int b){int r=1;for(;b;b>>=1,a=mul(a,a))if(b&1)r=mul(a,r);return r;}
int main()
{
int n=read(),k=read(),cnt=0,ans;f[n+1]=0;
for(int i=1;i<=n;++i) a[i]=read();
for(int i=n;i;cnt+=a[i--]) for(int j=2*i;j<=n;j+=i) a[i]^=a[j];
for(int i=n;i;--i) f[i]=mul(mul(n-i,f[i+1])+n,pow(i,P-2));
ans=std::min(cnt,k);
for(int i=cnt;i>k;--i) inc(ans,f[i]);
for(int i=1;i<=n;++i) ans=mul(ans,i);
printf("%d",ans);
}
寿司餐厅
Luogu
LOJ
BZOJ
首先价格里面的(cx)部分可以直接由(d_{i,i}leftarrow d_{i,i}-a_i)完成。
然后我们发现这就是个标准的最大权闭合子图,选(d_{i,j})必须选(d_{i,j-1},d_{i+1,j})。
然后考虑价格中(mx^2)这部分,建(max(a))个点,选(d_{i,i})必须选(-a_i)即可。
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=107,V=6507,E=60007,inf=1e9;
int a[N],id[N][N],d[N][N],s,t,tot=1,head[V],cur[V],dep[V];struct edge{int v,f,next;}e[E];std::queue<int>q;
int read(){int x;scanf("%d",&x);return x;}
void add(int u,int v,int f){e[++tot]={v,f,head[u]},head[u]=tot,e[++tot]={u,0,head[v]},head[v]=tot;}
int bfs()
{
memset(dep+1,0x3f,t<<2),memcpy(cur+1,head+1,t<<2),dep[s]=0,q.push(s);
for(int i,u,v;!q.empty();) for(u=q.front(),q.pop(),i=head[u];i;i=e[i].next) if(dep[v=e[i].v]>inf&&e[i].f) dep[v]=dep[u]+1,q.push(v);
return dep[t]<inf;
}
int dfs(int u,int lim)
{
if(!lim||u==t) return lim;
int v,flow=0;
for(int&i=cur[u],f;i;i=e[i].next)
if(dep[v=e[i].v]==dep[u]+1&&(f=dfs(v,std::min(lim,e[i].f))))
{
flow+=f,lim-=f,e[i].f-=f,e[i^1].f+=f;
if(!lim) break;
}
return flow;
}
int main()
{
int n=read(),m=read(),ans=0,cnt=0;s=n*(n+1)/2+1001,t=s+1;
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) for(int j=i;j<=n;++j) d[i][j]=read()-(i==j)*a[i],id[i][j]=++cnt;
for(int i=1;i<=n;++i) for(int j=i;j<=n;++j) if(i^j) add(id[i][j],id[i+1][j],inf),add(id[i][j],id[i][j-1],inf); else if(m) add(id[i][j],cnt+a[i],inf);
for(int i=1;i<=n;++i) for(int j=i;j<=n;++j) d[i][j]>0? ans+=d[i][j],add(s,id[i][j],d[i][j]):add(id[i][j],t,-d[i][j]);
if(m) for(int i=1;i<=1000;++i) add(cnt+i,t,i*i);
while(bfs()) ans-=dfs(s,inf);
printf("%d",ans);
}