口胡(然而有代码)<第三章>
他来了!
估计是最后一章了/kk。(诶,能去 NOIP 好像可以续命啊)
上期精彩:口胡(然而有代码)<第二章>
题目计数:\(150\)。
提供一种比较屑的 \(\mathcal O(n\log n)\) 解法。
当然常数很小。。。。
首先考虑到将这些数钦定为最小值,然后去找区间,易证答案一定来自于满足此值最小的最长区间。
我们于是可以从最大值开始枚举,判断他的两侧已经有多少被枚举过了(这些数一定比他大),然后算就完了。
可是如何判断已经被枚举的最左,最右在哪里呢。
我们把已被用的搞成区间,记录一下左右端点,然后不断计算,扩展即可得到答案。
具体见代码:
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define ll long long
#define MAXN 2000005
int n;
int num[MAXN];
int t[MAXN],l[MAXN],r[MAXN];
struct node
{
int val,id;
}a[MAXN];
int cnt=0;
ll ans=0;
bool cmp(node n,node m){return n.val>m.val;}
void hsh()
{
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++) num[++cnt]=a[i].id;
return;
}
int main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i].val),t[i]=a[i].val,a[i].id=i;
hsh();
for(int i=1;i<=n;i++)
{
int j=num[i],len=1;
if(l[j-1]) len=len+(j-l[j-1]);
if(r[j+1]) len=len+(r[j+1]-j);
ans=max(ans,(ll)len*(ll)t[j]);
if(l[j-1]&&r[j+1])
{
int op=l[j-1],rt=r[j+1];
l[j-1]=0,r[j+1]=0;
r[op]=rt,l[rt]=op;
}
else if(l[j-1])
{
int t=l[j-1];
l[j-1]=0,l[j]=t,r[t]=j;
}
else if(r[j+1])
{
int t=r[j+1];
r[j+1]=0,r[j]=t,l[t]=j;
}
else l[j]=r[j]=j;
}
printf("%lld\n",ans);
return 0;
}
\(102.\) AT5281 [ABC162F] Select Half
这里再提供一种比较屑的 dp 方法。
偶数就不说了,反正随便做做就好了。
奇数有点麻烦,
因为我们发现如果将整个序列划段(每段只选一个数),有一个段长度是 \(3\)。
那就好说了!
我们令 \(f_{i,0/1,0/1}\) 为划分到第 \(i\) 段是否划分出长度为 \(3\) 的段,该段的最后一位是否被选的最大获益。
对于第二维是 \(0\) 的部分,当偶数做,转移很简单。
现在主要来讨论第二维是 \(1\) 的部分。
显然可以得到:
最后答案是 \(\max\{f_{h,1,0},f_{h,1,1}\}\;\;(h=\dfrac{n-1}{2}\;\text{即组数})\)
然后转移就好了,时间复杂度是 \(\mathcal O(n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
#define ll long long
#define read(x) scanf("%d",&x)
int n,a[MAXN<<1];
ll dp[MAXN][2];
ll f[MAXN][2][2];
int main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i]);
if(!(n&1))
{
for(int i=1;i<=n/2;i++) dp[i][0]=dp[i][1]=-1ll<<60;
dp[1][0]=(ll)a[1],dp[1][1]=(ll)a[2];
for(int i=2;i<=n/2;i++)
{
dp[i][0]=(ll)a[i*2-1]+dp[i-1][0];
dp[i][1]=(ll)a[i*2]+max(dp[i-1][0],dp[i-1][1]);
}
printf("%lld\n",max(dp[n/2][0],dp[n/2][1]));
}
else
{
int h=(n-1)/2;
for(int i=1;i<=h;i++)
{
f[i][0][0]=f[i][0][1]=-1ll<<60;
f[i][1][0]=f[i][1][1]=-1ll<<60;
}
f[1][0][0]=(ll)a[1],f[1][0][1]=(ll)a[2];
f[1][1][0]=(ll)max(a[1],a[2]),f[1][1][1]=(ll)a[3];
for(int i=2;i<=h;i++)
{
f[i][0][0]=f[i-1][0][0]+(ll)a[i*2-1];
f[i][0][1]=(ll)a[i*2]+max(f[i-1][0][0],f[i-1][0][1]);
f[i][1][0]=f[i-1][1][0]+(ll)a[2*i];
f[i][1][0]=max(f[i][1][0],f[i-1][0][0]+(ll)max(a[2*i-1],a[2*i]));
f[i][1][0]=max(f[i][1][0],f[i-1][0][1]+(ll)a[2*i]);
f[i][1][1]=(ll)a[i*2+1]+max(f[i-1][1][0],f[i-1][1][1]);
f[i][1][1]=max(f[i][1][1],max(f[i-1][0][1],f[i-1][0][0])+(ll)a[2*i+1]);
}
printf("%lld\n",max(f[h][1][0],f[h][1][1]));
}
return 0;
}
\(103.\) P2845 [USACO15DEC]Switching on the Lights S
比较裸的广搜,直接搜即可。
需要注意的是开灯和能够到达是两个临界状态,都要继续搜。
我不会分析复杂度,猜一波 \(\mathcal O(n^2+m)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
int li[10005];
queue<int>q;
int n,m,tx,ty,sx,sy;
struct node
{
int to,nxt;
}e[200005];
int head[10005],cnt=0;
int vis[10005],ans=0;
int ch[10005];
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void bfs(int u)
{
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
li[j]=1;
if(!vis[j]&&ch[j]) bfs(j);
}
int x=u/n+1,y=u%n;
if(!y) y=n,x--;
if(x>1&&!vis[(x-2)*n+y])
{
ch[(x-2)*n+y]=1;
if(li[(x-2)*n+y]) bfs((x-2)*n+y);
}
if(x<n&&!vis[x*n+y])
{
ch[x*n+y]=1;
if(li[x*n+y]) bfs(x*n+y);
}
if(y>1&&!vis[(x-1)*n+y-1])
{
ch[(x-1)*n+y-1]=1;
if(li[(x-1)*n+y-1]) bfs((x-1)*n+y-1);
}
if(y<n&&!vis[(x-1)*n+y+1])
{
ch[(x-1)*n+y+1]=1;
if(li[(x-1)*n+y+1]) bfs((x-1)*n+y+1);
}
return;
}
int main()
{
read(n),read(m);
for(int i=1;i<=m;i++)
{
read(tx),read(ty),read(sx),read(sy);
add((tx-1)*n+ty,(sx-1)*n+sy);
}
ch[1]=1,vis[1]=1,li[1]=1,q.push(1);
bfs(1);
for(int i=1;i<=n*n;i++) if((li[i])) ans++;
printf("%d\n",ans);
return 0;
}
在每个节点维护小根堆,然后暴力转移即可。
没有代码,复杂度是 \(\mathcal O(n\log n)\)。
\(105.\) CF817C Really Big Numbers
这玩意一猜就有单调性啊,不信你可以手玩一下。
而且发现还是 \(10\) 个数一段的那种。
所以二分一下边界即可,时间复杂度是 \(\mathcal O(\log n)\)。
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
using namespace std;
#define ll long long
#define read(x) scanf("%lld",&x)
#define MAXN 1000000000000000002
ll n,s;
ll f(ll a)
{
ll sum=a;
while(a)
{
sum=sum-a%10;
a/=10;
}
return sum;
}
int main()
{
read(n),read(s);
ll l=1ll,r=MAXN,mid;
while(l<r)
{
mid=(l+r)>>1;
if(f(mid)<s) l=mid+1;
else r=mid;
}
printf("%lld\n",max((ll)0,n-l+1));
return 0;
}
\(106.\) P4643 [国家集训队]阿狸和桃子的游戏
这是在集训中出的一道题,今天才知道是原题。
当时看到博弈论就萎了,最后听讲解感到很妙,
可是这也不至于黑吧......
就是把边权平分到两点上,使得差不变,然后把点权排序,贪心选择即可。
代码随便写就行了。
\(107.\) AT2581 [ARC075C] Meaningful Mean
容易想到每次更新以某个位置为结尾的所有子段的和。
然维护不了,考虑少加前面的,给后面的加上,和仍不变。
我们容易想到维护一种二分结构,使得找出边界。
将所有可行状态求出然后树状数组/线段树做就好了。
什么?你不想离线离散化,平衡树可以啊!
\(108.\) P2439 [SDOI2005]阶梯教室设备利用
用二分或者是线段树维护一下这个 dp,就做完了,反正很套路...
可以当 \(01\) Trie 板子题来做了,话说这玩意这么普及吗,就我不会了。
\(01\) Trie 可以做到 \(\mathcal O(n\log n)\)
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 100005
int n;
struct node
{
int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int val[MAXN],ans=0;
int u,v,c;
struct Trie
{
int son[MAXN*35][2],opt;
Trie(){opt=0;memset(son,0,sizeof(son));}
void insert(int s)
{
int p=0;
for(int i=30;i>=0;i--)
{
int rt=((1<<i)&s)?1:0;
if(!son[p][rt]) son[p][rt]=++opt;
p=son[p][rt];
}
}
int getmax(int s)
{
int ans=0,p=0;
for(int i=30;i>=0;i--)
{
int rt=(s&(1<<i))?0:1;
if(son[p][rt]) ans+=(1<<i),p=son[p][rt];
else p=son[p][rt^1];
}
return ans;
}
}T;
void add(int u,int v,int w){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt,e[cnt].w=w;}
void dfs(int cur,int fa,int sum)
{
val[cur]=sum;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
T.insert(sum^e[i].w),dfs(j,cur,sum^e[i].w);
}
return;
}
int main()
{
read(n);
for(int i=1;i<n;i++)
{
read(u),read(v),read(c);
add(u,v,c),add(v,u,c);
}
dfs(1,0,0);
for(int i=1;i<=n;i++) ans=max(ans,T.getmax(val[i]));
printf("%d\n",ans);
return 0;
}
考虑压栈,然后不断把上一个和这一个中间的长度给加上。
然后更新一下从这个位置开始最远到哪里,于是就能实现 \(\mathcal O(1)\) 的转移了。
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 1000005
int ans=-2147483647;
int st[MAXN],top=0;
char c[MAXN];
int n,num,len[MAXN];
int main()
{
scanf("%s",c+1);
n=strlen(c+1);
for(int i=1;i<=n;i++)
{
if(c[i]=='['||c[i]=='(') st[++top]=i;
else if(c[i]==']'&&c[st[top]]!='[') st[++top]=i;
else if(c[i]==')'&&c[st[top]]!='(') st[++top]=i;
else
{
int op=i-st[top]+1;
if(len[st[top]-1]) op+=len[st[top]-1];
len[i]=op,top--;
if(ans<op) ans=op,num=i;
}
}
for(int i=num-ans+1;i<=num;i++) printf("%c",c[i]);
return puts(""),0;
}
\(111.\) CF518D Ilya and Escalator
这里重点讲一下 \(t\leq n\) 时的 \(\mathcal O(1)\) 做法,对于其他情况用 \(\mathcal O(n^2)\) 解法即可(其实也有式子,只是不适合此题)。
当 \(t\leq n\) 时,人一定够用。
考虑期望的定义,在这个题中,容易知道;
考虑组合数的一个性质
考虑将式子枚举项改变:
我们发现提出一个 \(p\) 就能构造二项式定理:
于是有:
故直接输出 \(tp\) 即可。
给出代码:
#include"cstdio"
#include"cmath"
#include"cstring"
#include"iostream"
using namespace std;
#define MAXN 2005
double p,q;
int n,t;
double dp[MAXN][MAXN];
int main()
{
scanf("%d%lf%d",&n,&p,&t);
if(t<=n) return printf("%.6lf\n",p*t),0;
else
{
for(int i=1;i<=t;i++) dp[i][0]=0.00;
for(int j=1;j<=n;j++) dp[0][j]=0.00;
for(int i=1;i<=t;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=(p*(dp[i-1][j-1]+1.00)+(1-p)*dp[i-1][j]);
}
}
printf("%.6lf\n",dp[t][n]);
}
return 0;
}
悬线法,从前学过没写过,故来补一补。
可以做到 \(\mathcal O(n^2)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 2005
#define read(x) scanf("%d",&x)
int le[MAXN][MAXN],ri[MAXN][MAXN],up[MAXN][MAXN];
int a[MAXN][MAXN],n,m;
int ans1,ans2;
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
read(a[i][j]);
le[i][j]=ri[i][j]=j;
up[i][j]=1;
}
}
for(int i=1;i<=n;i++)
{
for(int j=2;j<=m;j++)
{
if(a[i][j]^a[i][j-1]) le[i][j]=le[i][j-1];
}
for(int j=m-1;j>=1;j--)
{
if(a[i][j]^a[i][j+1]) ri[i][j]=ri[i][j+1];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(i>1&&(a[i][j]^a[i-1][j]))
{
up[i][j]=up[i-1][j]+1;
le[i][j]=max(le[i][j],le[i-1][j]);
ri[i][j]=min(ri[i][j],ri[i-1][j]);
}
int x=ri[i][j]-le[i][j]+1,y=up[i][j];
ans1=max(ans1,min(x,y)*min(x,y));
ans2=max(ans2,x*y);
}
}
printf("%d\n%d\n",ans1,ans2);
return 0;
}
还是悬线法(
时间复杂度是 \(\mathcal O(n^2)\)
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 1005
int n,m;
char a[MAXN][MAXN];
int le[MAXN][MAXN],ri[MAXN][MAXN],up[MAXN][MAXN];
int ans=0;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
if(a[i][j]=='F')
{
up[i][j]=1;
le[i][j]=ri[i][j]=j;
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=2;j<=m;j++)
{
if(a[i][j]=='F'&&a[i][j-1]=='F') le[i][j]=le[i][j-1];
}
for(int j=m;j>=2;j--)
{
if(a[i][j]=='F'&&a[i][j+1]=='F') ri[i][j]=ri[i][j+1];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(i>1&&a[i][j]=='F'&&a[i-1][j]=='F')
{
up[i][j]=up[i-1][j]+1;
le[i][j]=max(le[i][j],le[i-1][j]);
ri[i][j]=min(ri[i][j],ri[i-1][j]);
}
int x=up[i][j],y=ri[i][j]-le[i][j]+1;
ans=max(ans,x*y);
}
}
printf("%d\n",ans*3);
return 0;
}
\(114.\) P4928 [MtOI2018]衣服?身外之物!
小数据范围容易想到状压。
而且发现 \(y\) 很小,所以我们状压的方向也就确定了。
我们考虑将一个四位数字(可以认为是 \(7\) 进制数字)的每一位代表一个衣服还差几天就能洗完,特殊的,如果这一位是 \(0\),说明对应的衣服可用。
我们只需要枚举所有可行的下一步,更新一下就好了(虽然模拟起来比较麻烦)。
我比较菜,复杂度是 \(\mathcal O(7^nmn^2)\),,,但是可过。
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define ll long long
#define MAXN 2005
ll dp[2405][MAXN];
int n,m,sta=0;
int x[10],y[10],z[MAXN];
ll ans=-1ll<<62;
int main()
{
for(int i=0;i<=2400;i++)
{
for(int j=0;j<=2000;j++) dp[i][j]=-1ll<<62;
}
dp[0][0]=0;
read(n),read(m);
for(int i=1;i<=n;i++) read(x[i]);
for(int i=1;i<=n;i++) read(y[i]);
for(int i=1;i<=m;i++) read(z[i]);
for(int i=0;i<m;i++)
{
for(int s=0;s<2400;s++)
{
if(dp[s][i]<(-1ll<<60)) continue;
int a[5];
memset(a,0,sizeof(a));
int ns=s;
a[1]=ns/343,ns%=343;
a[2]=ns/49,ns%=49;
a[3]=ns/7,ns%=7;
a[4]=ns;
for(int k=4;k>=4-n+1;k--)
{
if(!a[k])
{
int b[5];
memset(b,0,sizeof(b));
b[k]=y[4-k+1];
for(int o=4;o>=4-n+1;o--)
{
if(o!=k&&a[o]) b[o]=a[o]-1;
}
int ss=b[1]*343+b[2]*49+b[3]*7+b[4];
dp[ss][i+1]=max(dp[ss][i+1],dp[s][i]+z[i+1]*x[4-k+1]);
}
}
}
}
for(int i=0;i<2400;i++) ans=max(ans,dp[i][m]);
if(ans<(-1ll<<60)) printf("gcd loves her clothes!");
else printf("%lld\n",ans);
return 0;
}
构造线性递推式即可,好神仙啊。
当然是预处理出斜率和截距辣!
时间复杂度是 \(\mathcal O(n)\)。
#include"cstdio"
#include"cmath"
#include"iostream"
#include"algorithm"
using namespace std;
#define ll long long
int n,lst=-1;
int rt[35][5],top[5];
int w[10];
char t[5];
ll f[35];
int main()
{
scanf("%d",&n);
for(int i=1;i<=6;i++)
{
scanf("%s",t);
int op=t[0]-'A'+1,rt=t[1]-'A'+1;
w[i]=op*10+rt;
}
f[1]=1;
for(int e=2;e<=3;e++)
{
top[1]=e,top[2]=top[3]=0,lst=-1;
ll ans=0;
rt[0][1]=rt[0][2]=rt[0][3]=0x7fffffff;
for(int i=1;i<=e;i++) rt[i][1]=e-i+1;
while(top[2]<e&&top[3]<e)
{
for(int i=1;i<=6;i++)
{
int u=w[i]/10,v=w[i]%10;
if(!top[u]) continue;
if(rt[top[u]][u]>rt[top[v]][v]) continue;
if(lst==rt[top[u]][u]) continue;
lst=rt[top[u]][u];
int s=rt[top[u]--][u];
rt[++top[v]][v]=s;
ans++;
break;
}
}
f[e]=ans;
}
if(n<=3) return printf("%lld\n",f[n]),0;
ll k=(f[3]-f[2])/(f[2]-f[1]),b=f[2]-k*f[1];
for(int i=4;i<=n;i++) f[i]=k*f[i-1]+b;
printf("%lld\n",f[n]);
return 0;
}
考虑最小生成树,我们把这些限制点连起来,然后最小生成树就好了,时间复杂度是 \(\mathcal O(n\log n)\)。
#include"cstdio"
#include"cmath"
#include"iostream"
#include"algorithm"
using namespace std;
#define ll long long
#define MAXN 100005
#define read(x) scanf("%d",&x)
int n,k;
int f[MAXN];
struct node
{
int u,v,w;
}e[MAXN];
ll ans;
int x,y;
bool cmp(node n,node m){return n.w>m.w;}
int getf(int u){return (f[u]==u)?u:f[u]=getf(f[u]);}
int main()
{
read(n),read(k);
for(int i=1;i<=n;i++) f[i]=i;
read(x),y=x+1;
for(int i=2;i<=k;i++) read(x),f[x+1]=y,y=x+1;
for(int i=1;i<n;i++)
{
read(e[i].u),read(e[i].v),read(e[i].w);
e[i].u++,e[i].v++;
}
sort(e+1,e+n,cmp);
for(int i=1;i<n;i++)
{
int t1=getf(e[i].u),t2=getf(e[i].v);
if(t1!=t2) f[t2]=t1;
else ans+=(ll)e[i].w;
}
printf("%lld\n",ans);
return 0;
}
\(117.\) P1641 [SCOI2010]生成字符串
折线法类比卡特兰数。
注意减去多余情况时,最好看起点,比较好看,否则看终点很麻烦(当然同样行的通......)。
得到答案 :\(\dbinom{n+m}{n}-\dbinom{n+m}{m-1}\)。
我这种比较垃圾的实现是 \(\mathcal O(n\log n)\) 的。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define ll long long
#define MAXN 2000005
#define MOD 20100403
ll fac[MAXN];
int n,m;
ll quickpow(ll a,ll b)
{
ll ans=1ll,base=a%MOD;
while(b)
{
if(b&1) ans=ans*base%MOD;
base=base*base%MOD;
b>>=1;
}
return ans%MOD;
}
ll inv(ll a){return quickpow(a,MOD-2);}
void init(int maxn){fac[0]=1ll;for(int i=1;i<=maxn;i++) fac[i]=fac[i-1]*(ll)i%MOD;return;}
ll C(int n,int m){return fac[n]*inv(fac[m])%MOD*inv(fac[n-m])%MOD;}
int main()
{
scanf("%d%d",&n,&m);
init(n+m+1);
printf("%lld\n",(C(n+m,n)-C(n+m,m-1)+MOD)%MOD);
return 0;
}
\(118.\) P3200 [HNOI2009]有趣的数列
发现是卡特兰数,然而模数不保证是质数比较谔谔。
考虑用线性筛约分(太太太妙了!),时间复杂度是 \(\mathcal O(n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define ll long long
#define MAXN 2000005
ll ans=1ll;
int e[MAXN],pr[MAXN],op=0;
int cnt[MAXN];
int n,p;
void init(int mn)
{
for(int i=2;i<=mn;i++)
{
if(!e[i]) e[i]=i,pr[++op]=i;
for(int j=1;j<=op&&pr[j]*i<=mn;j++)
{
e[pr[j]*i]=pr[j];
if(i%pr[j]==0) break;
}
}
}
ll quickpow(ll a,int b)
{
ll ans=1ll,base=a;
while(b)
{
if(b&1) ans=ans*base%p;
b>>=1;
base=base*base%p;
}
return ans%p;
}
int main()
{
scanf("%d%d",&n,&p);
init(2*n+1);
for(int i=2;i<=n;i++) cnt[i]=-1;
for(int i=n+2;i<=2*n;i++) cnt[i]=1;
for(int i=2*n;i>=2;i--)
{
if(e[i]<i)
{
cnt[e[i]]+=cnt[i];
cnt[i/e[i]]+=cnt[i];
}
}
for(int i=1;i<=op;i++)
{
if(cnt[pr[i]])
{
ans=ans*quickpow(pr[i],cnt[pr[i]])%p;
}
}
printf("%lld\n",ans%p);
return 0;
}
除了最后一个点特判掉,其他都挺好的......
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
int n,m,u;
int in[50],pos[50];
char c[5]={'0','A','B','C'};
int tot=0;
void dfs(int x,int y)
{
if(in[x]==y) return;
for(int i=x-1;i>=1;i--) dfs(i,6-y-in[x]);
printf("move %d from %c to %c\n",x,c[in[x]],c[y]);
in[x]=y,tot++;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=3;i++)
{
scanf("%d",&m);
for(int j=1;j<=m;j++) scanf("%d",&u),in[u]=i;
}
for(int i=1;i<=3;i++)
{
scanf("%d",&m);
for(int j=1;j<=m;j++) scanf("%d",&u),pos[u]=i;
}
if(n==3&&in[3]==1&&pos[3]==3&&in[2]==3&&pos[2]==1)
{
printf("move 3 from A to B\n");
printf("move 1 from C to B\n");
printf("move 2 from C to A\n");
printf("move 1 from B to A\n");
printf("move 3 from B to C\n");
printf("5\n");
return 0;
}
for(int i=n;i>=1;i--) if(in[i]!=pos[i]) dfs(i,pos[i]);
printf("%d\n",tot);
return 0;
}
别问我为什么这么想,感觉挺自然的,然后 \(\mathcal O(n)\) 加个 fread 上去就能拿到 rk1了/se
但是很难调,不知道为什么还要特判/kk
#include"cstdio"
#include"cmath"
#include"iostream"
#include"cstring"
#include"ctime"
#include"algorithm"
using namespace std;
#define MAXN 100005
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
int n,k,t;
int u,v;
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int sum=0,ans=0x7fffffff;
int vis[MAXN],root;
double s;
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
return x;
}
inline void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u];head[u]=cnt;}
inline int dfs(int cur,int fa)
{
int op=-114514;
int minx=114514;//IEE
for(register int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
int now=dfs(j,cur);
op=max(now,op);
if(now<0)
{
minx=min(minx,now);
continue;
}
}
if(op+minx<=-1) return minx+1;
if(op==k)
{
++sum;
return -k;
}
if(fa==0&&op>=0) sum++;
if(op<-100) return 1;
return op+1;
}
int main()
{
n=read(),k=read(),t=read();
for(register int i=1;i<n;++i) u=read(),v=read(),add(u,v),add(v,u);
if(k==0)
{
printf("%d\n",n);
return 0;
}
dfs(1,0);
printf("%d\n",sum);
return 0;
}
哈夫曼树板子题。
借用合并果子的 trick 实现,当然,点不够而加点的 idea 十分值得学习。
时间复杂度是 \(\mathcal O(n\log n)\)(大概吧)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
#include"vector"
using namespace std;
#define ll long long
#define read(x) scanf("%lld",&x)
int n,k;
ll ans=0,now=0;
int cnt=0;
ll val;
priority_queue<pair<ll,ll>,vector<pair<ll,ll> >,greater<pair<ll,ll> > >q;
int main()
{
read(n),read(k);
for(int i=1;i<=n;i++) read(val),q.push(make_pair(val,1ll));
if(n%(k-1)!=1&&k>2)
{
int op;
if(!(n%(k-1))) op=1;
else op=k-n%(k-1);
//k-n%(k-1);
cnt=op;
for(int i=1;i<=op;i++) q.push(make_pair(0,1ll));
}
cnt+=n;
while(q.size()!=1)
{
ll now=0,h=0;
for(int i=1;i<=k;i++)
{
now+=q.top().first,h=max(h,q.top().second);
q.pop();
}
ans+=now;
q.push(make_pair(now,h+1));
}
printf("%lld\n%lld\n",ans,q.top().second-1ll);
return 0;
}
容斥经典题,背包之后去除多余状态就好。
时间复杂度是 \(\mathcal O(\max s+2^4n)\)。
#include"cstdio"
#include"iostream"
#include"cmath"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
#define ll long long
#define MAXN 100005
ll dp[MAXN];
int c[5],d[5],s,n,ss;
int cnt[5];
int main()
{
for(int i=1;i<=4;i++) read(c[i]);
read(n),dp[0]=1ll;
for(int i=1;i<=4;i++)
{
for(int j=1;j<=100001;j++)
if(j>=c[i]) dp[j]=dp[j]+dp[j-c[i]];
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<=4;i++) read(d[i]);
read(ss);
ll ans=dp[ss];
for(int s=1;s<=15;s++)
{
int opt=0,tot=0;
memset(cnt,0,sizeof(cnt));
for(int i=0;i<4;i++)
cnt[i+1]=((1<<i)&s)?1:0,opt+=cnt[i+1];
for(int i=1;i<=4;i++)
if(cnt[i]) tot+=c[i]*(d[i]+1);
if(tot<=ss)//等于也要判断,因为dp[0]=1,有意义
{
int op=(opt&1)?-1:1;
ans+=dp[ss-tot]*(ll)op;
}
}
printf("%lld\n",ans);
}
return 0;
}
考虑容斥用总方案数减去不合法的方案数就是最终答案。
不合法的方案数比较好求,我们考虑只记录最多的那一列比其他的多多少。
为什么不能直接求合法的呢?
因为我们在计数时,只是粗略的统计,如果所谓最多的那一列其实在统计中已经不是最多的了,那就完蛋了。
可以用 \(\mathcal O(n^2m)\) 的复杂度计算。
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define B 100
#define read(x) scanf("%d",&x)
#define ll long long
#define MOD 998244353
int n,m,a[105][2005];
ll dp[105][205];
ll sum[105];
ll ans=1ll;
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
read(a[i][j]),sum[i]=(sum[i]+a[i][j])%MOD;
ans=ans*(sum[i]+1ll)%MOD;
}
ans=(ans-1+MOD)%MOD;//减去没有选的情况
for(int i=1;i<=m;i++)
{
memset(dp,0,sizeof(dp));
dp[0][n]=1ll;
for(int j=1;j<=n;j++)
{
for(int k=0;k<=2*n;k++)
{
dp[j][k]=dp[j-1][k];
dp[j][k]=(dp[j][k]+(ll)(sum[j]-a[j][i]+MOD)*dp[j-1][k+1]%MOD)%MOD;
dp[j][k]=(dp[j][k]+(ll)a[j][i]*dp[j-1][k-1])%MOD;
}
}
for(int j=n+1;j<=2*n;j++) ans=(ans-dp[n][j]+MOD)%MOD;
}
printf("%lld\n",ans%MOD);
return 0;
}
感觉很套路的缩点和 DAG 上 dp,但是因为没有看到从 \(1\sim n\) 而去世,希望以后不会犯这样的错误/kk。
时间复杂度是 \(\mathcal O(n+m)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
#include"queue"
using namespace std;
#define read(x) scanf("%d",&x)
#define N 100005
#define M 500005
int n,m;
int u[M],v[M],z;
struct node
{
int to,nxt;
}e[M<<1];
int head[N],cnt=0;
int low[N],num[N];
int be[N],mu[N];
int idx=0,tot=0;
int val[N],maxn[N],minx[N];
int st[N],top=0;
int in[N],dp[N];
queue<int>q;
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void dfs(int cur)
{
low[cur]=num[cur]=++idx;
st[++top]=cur;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(!low[j]) dfs(j);
if(!be[j]) low[cur]=min(low[cur],low[j]);
}
if(low[cur]==num[cur])
{
tot++;
while(1)
{
int u=st[top--];
be[u]=tot,maxn[tot]=max(maxn[tot],val[u]);
minx[tot]=min(minx[tot],val[u]);
if(u==cur) break;
}
}
return;
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(val[i]),maxn[i]=0,minx[i]=0x7fffffff;
for(int i=1;i<=m;i++)
{
read(u[i]),read(v[i]),read(z);
if(z==1) add(u[i],v[i]);
else add(u[i],v[i]),add(v[i],u[i]);
}
for(int i=1;i<=n;i++) if(!low[i]) dfs(i);
memset(head,0,sizeof(head));
for(int i=1;i<=m;i++)
{
if(be[u[i]]!=be[v[i]]) add(be[u[i]],be[v[i]]),in[be[v[i]]]++;
}
for(int i=1;i<=tot;i++) if(!in[i]) q.push(i);
while(!q.empty())
{
int up=q.front();
q.pop();
dp[up]=max(dp[up],maxn[up]-minx[up]);
for(int i=head[up];i;i=e[i].nxt)
{
int j=e[i].to;
minx[j]=min(minx[j],minx[up]);
dp[j]=max(dp[j],dp[up]);
in[j]--;
if(!in[j]) q.push(j);
}
}
printf("%d\n",dp[be[n]]);
return 0;
}
\(125.\) P2279 [HNOI2003]消防局的设立
将军令那个题的弱化版,很简单得抄了一份代码......
#include"cstdio"
#include"cmath"
#include"iostream"
#include"cstring"
#include"ctime"
#include"algorithm"
using namespace std;
#define MAXN 100005
int n;
int u,v;
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int sum=0,ans=0x7fffffff;
int vis[MAXN],root;
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
return x;
}
inline void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u];head[u]=cnt;}
inline int dfs(int cur,int fa)
{
int op=-114514;
int minx=114514;//IEE
for(register int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
int now=dfs(j,cur);
op=max(now,op);
if(now<0)
{
minx=min(minx,now);
continue;
}
}
if(op+minx<=-1) return minx+1;
if(op==2)
{
++sum;
return -2;
}
if(fa==0&&op>=0) sum++;
if(op<-100) return 1;
return op+1;
}
int main()
{
n=read();
for(register int i=2;i<=n;++i) u=read(),add(u,i),add(i,u);
dfs(1,0);
printf("%d\n",sum);
return 0;
}
递推数列,考虑引进数列上其他一个数为未知变量,递推后最后得到一元一次方程,解出来即可。
时间复杂度是 \(\mathcal O(n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 65
int n,m;
double x[MAXN],y[MAXN],z[MAXN];
double a,b,c;
int main()
{
scanf("%d%d",&n,&m);
scanf("%lf%lf%lf",&a,&b,&c);
x[1]=y[2]=1.00;
x[2]=y[1]=z[1]=z[2]=0;
for(int i=3;i<=n;i++)
{
x[i]=x[i-2]-2.0*x[i-1];
y[i]=y[i-2]-2.0*y[i-1];
z[i]=z[i-2]-2.0*z[i-1]+2.00;
}
double tmp=(c-x[n]*b-z[n]*a)/y[n];
if(m==2) printf("%.3lf\n",tmp);
else if(m==1) printf("%.3lf\n",b);
else if(m==n) printf("%.3lf\n",c);
else printf("%.3lf\n",x[m]*b+y[m]*tmp+z[m]*a);
return 0;
}
\(127.\) P2922 [USACO08DEC]Secret Message G
用 Trie 数来计数。
注意最后统计答案时把一些多算的东西减去,时间复杂度是 \(\mathcal O(\sum len)\)。
#include"iostream"
#include"cstdio"
#include"cstring"
using namespace std;
#define MAXN 500005
#define read(x) scanf("%d",&x)
#define mem(e) memset(e,0,sizeof(e))
int n,m;
int b[MAXN],k;
struct Trie
{
int son[MAXN][2],cnt[MAXN],ed[MAXN];
int opt;
Trie(){mem(cnt),mem(son),mem(ed),opt=0;}
void insert(int s[],int len)
{
int p=0;
for(int i=1;i<=len;i++)
{
if(!son[p][s[i]]) son[p][s[i]]=++opt;
p=son[p][s[i]];
cnt[p]++;
}
ed[p]++;
return;
}
int find(int s[],int len)
{
int p=0,ans=0;
for(int i=1;i<=len;i++)
{
if(!son[p][s[i]]) return ans;
p=son[p][s[i]];
ans+=ed[p];
}
return cnt[p]+ans-ed[p];
}
}T;
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++)
{
read(k);
for(int j=1;j<=k;j++) read(b[j]);
T.insert(b,k);
}
for(int i=1;i<=m;i++)
{
read(k);
for(int j=1;j<=k;j++) read(b[j]);
printf("%d\n",T.find(b,k));
}
return 0;
}
\(128.\) P4145 上帝造题的七分钟2 / 花神游历各国
是 GSS4 的翻版,所以代码直接......
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define ll long long
#define MAXN 100005
struct node
{
ll sum,rec;
}a[MAXN<<2];
int n,m;
ll t[MAXN];
int cnt=0,l,r,v;
void update(int k){a[k].sum=a[k<<1].sum+a[k<<1|1].sum,a[k].rec=max(a[k<<1].rec,a[k<<1|1].rec);}
void build(int k,int l,int r)
{
if(l==r){a[k].sum=a[k].rec=t[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
return;
}
void turn(int k,int l,int r,int x,int y)
{
if(x==y){a[k].sum=a[k].rec=sqrt(a[k].sum);return;}
int mid=(x+y)>>1;
if(a[k<<1].rec>1)
{
if(r<=mid) turn(k<<1,l,r,x,mid);
else if(l<=mid) turn(k<<1,l,mid,x,mid);
}
if(a[k<<1|1].rec>1)
{
if(l>mid) turn(k<<1|1,l,r,mid+1,y);
else if(r>mid) turn(k<<1|1,mid+1,r,mid+1,y);
}
update(k);
}
ll query(int k,int l,int r,int x,int y)
{
if(x==l&&y==r) return a[k].sum;
int mid=(x+y)>>1;
if(r<=mid) return query(k<<1,l,r,x,mid);
else if(l>mid) return query(k<<1|1,l,r,mid+1,y);
else return query(k<<1,l,mid,x,mid)+query(k<<1|1,mid+1,r,mid+1,y);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&t[i]);
build(1,1,n);
read(m);
for(int i=1;i<=m;i++)
{
read(v),read(l),read(r);
if(l>r) swap(l,r);
if(!v) turn(1,l,r,1,n);else printf("%lld\n",query(1,l,r,1,n));
}
puts("");
return 0;
}
\(129.\) P5201 [USACO19JAN]Shortcut G
具体方法见我的丢人现场:Link (不是前缀,是前驱)
这种方法应该是一种比较劣的最短路树构建方法,好在是自己 yy 的,也轻松很累地过掉了此题,算是一种新思路。
时间复杂是 \(\mathcal O(n\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define N 10005
#define M 50005
#define inf 1ll<<62
#define ll long long
#define read(x) scanf("%d",&x)
int n,m,u,v,w,t;
int we[N];
ll dis[N];
int vis[N],lst[N];
struct node
{
int to,nxt,w;
}e[M<<1];
int head[N],cnt=0;
ll ans=0;
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
priority_queue<pair<ll,int> >qq;
void add(int u,int v,int w){e[++cnt].to=v,e[cnt].nxt=head[u],e[cnt].w=w;head[u]=cnt;}
void dij()
{
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+(ll)e[i].w)
{
lst[j]=u,dis[j]=dis[u]+(ll)e[i].w;
q.push(make_pair(dis[j],j));
}
else if(dis[j]==dis[u]+(ll)e[i].w&&lst[j]>u) lst[j]=u;
}
}
return;
}
void work()
{
while(!qq.empty())
{
int u=qq.top().second;
qq.pop();
we[lst[u]]+=we[u];
}
return;
}
int main()
{
read(n),read(m),read(t);
for(int i=1;i<=n;i++) read(we[i]);
for(int i=1;i<=m;i++)
{
read(u),read(v),read(w);
add(u,v,w),add(v,u,w);
}
for(int i=1;i<=n;i++) dis[i]=inf;
dis[1]=0,q.push(make_pair(0,1));
dij();
for(int i=1;i<=n;i++) qq.push(make_pair(dis[i],i));
work();
for(int i=1;i<=n;i++)
{
if(t<dis[i]) ans=max(ans,(ll)(dis[i]-t)*(ll)we[i]);
}
printf("%lld\n",ans);
return 0;
}
我显然是 ha 子,我们都把前驱给找到了,为什么不直接连接前驱和他呢,这就建出了最短路树,显然要求的就是子节点(包括他自己)总权重乘上那个差值就好了。
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define N 10005
#define M 50005
#define inf 1ll<<62
#define ll long long
#define read(x) scanf("%d",&x)
int n,m,u,v,w,t;
int we[N];
ll dis[N];
int vis[N],lst[N];
struct node
{
int to,nxt,w;
}e[M<<1];
int head[N],cnt=0;
ll ans=0;
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
void add(int u,int v,int w){e[++cnt].to=v,e[cnt].nxt=head[u],e[cnt].w=w;head[u]=cnt;}
void dij()
{
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+(ll)e[i].w)
{
lst[j]=u,dis[j]=dis[u]+(ll)e[i].w;
q.push(make_pair(dis[j],j));
}
else if(dis[j]==dis[u]+(ll)e[i].w&&lst[j]>u) lst[j]=u;
}
}
return;
}
int dfs(int cur,int fa)
{
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
we[cur]+=dfs(j,cur);
}
ans=max(ans,(ll)we[cur]*(dis[cur]-t));
return we[cur];
}
int main()
{
read(n),read(m),read(t);
for(int i=1;i<=n;i++) read(we[i]);
for(int i=1;i<=m;i++)
{
read(u),read(v),read(w);
add(u,v,w),add(v,u,w);
}
for(int i=1;i<=n;i++) dis[i]=inf;
dis[1]=0,q.push(make_pair(0,1));
dij();
memset(head,0,sizeof(head)),cnt=0;
for(int i=2;i<=n;i++) add(i,lst[i],0),add(lst[i],i,0);
dfs(1,0);
printf("%lld\n",ans);
return 0;
}
集训时考到,原题教一下。
代码可能比较难看/kk。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define ll long long
int m;
ll f[105];
ll ca[105],cb[105];
int cna=0,cnb=0;
ll l,r,ans;
int main()
{
f[1]=1,f[0]=0;
for(int i=2;i<=60;i++) f[i]=f[i-1]+f[i-2];
scanf("%d",&m);
while(m--)
{
memset(ca,0,sizeof(ca)),memset(cb,0,sizeof(cb)),cna=1,cnb=1;
scanf("%lld%lld",&l,&r);
ca[1]=l,cb[1]=r;
int now=59;
while(l>2)
{
for(int i=now;i>=1;i--)
{
if(l>f[i])
{
l=ca[++cna]=l%f[i];
now=i;
break;
}
}
}
if(l==2&&ca[cna]!=2) ca[++cna]=2;
if(ca[1]!=1) ca[++cna]=1;
now=59;
while(r>2)
{
for(int i=now;i>=1;i--)
{
if(r>f[i])
{
r=cb[++cnb]=r%f[i];
now=i;
break;
}
}
}
if(r==2&&cb[cnb]!=2) cb[++cnb]=2;
if(cb[1]!=1) cb[++cnb]=1;
int i=1,j=1;
while(1)
{
if(ca[i]==cb[j])
{
ans=ca[i];
break;
}
if(ca[i]>cb[j]) i++;
else j++;
}
printf("%lld\n",ans);
}
return 0;
}
\(131.\) CF1113B Sasha and Magnetic Machines
考虑到值域很小,所以枚举值域内每个数即可,时间复杂度是 \(\mathcal O(a^3)\)。
膜一发 lyj
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 50005
int n,a[MAXN];
int ans=0,sum=0;
int cnt[105];
int main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i]),cnt[a[i]]++,sum+=a[i];
for(int i=1;i<=100;i++)
{
if(!cnt[i]) continue;
for(int j=1;j<=100;j++)
{
if(!cnt[j]) continue;
if(j==i) continue;
for(int k=2;k<=i;k++)
{
if(i%k!=0) continue;
ans=max(ans,i-i/k-(j*k-j));
}
}
}
printf("%d\n",sum-ans);
return 0;
}
\(132.\) P4873 [USACO14DEC]Cow Jog G
最长不上升子序列问题,要记住带等号,,,,,
时间复杂度是 \(\mathcal O(n\log n)\)。
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
#define ll long long
#define read(x) scanf("%d",&x)
int n;
int t,p,v;
struct node
{
ll o,r;
}a[MAXN];
ll minx=1ll<<62;
ll rt[MAXN];
int top=1;
bool cmp(node n,node m){return n.o<m.o;}
int main()
{
read(n),read(t);
for(int i=1;i<=n;i++)
{
read(p),read(v);
a[i].o=(ll)p,a[i].r=(ll)p+(ll)t*(ll)v;
}
sort(a+1,a+n+1,cmp);
rt[top]=a[1].r;
for(int i=2;i<=n;i++)
{
if(a[i].r<=rt[top])
{
rt[++top]=a[i].r;
continue;
}
int l=1,r=top,mid;
while(l<r)
{
mid=(l+r)>>1;
if(rt[mid]>=a[i].r) l=mid+1;
else r=mid;
}
rt[l]=a[i].r;
}
printf("%d\n",top);
return 0;
}
\(133.\) P7009 [CERC2013]Magical GCD
区间 \(\gcd\) 个数只有 \(\log n\) 个就太好了!
考虑用 \(st\) 表维护区间最大公约数,二分来确定边界,这个题就有了 \(\mathcal O(n\log ^3n)\) 的神仙解法(所以当然不是我自己想出的/kk)。
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
#define ll long long
int n,t;
ll a[MAXN];
ll st[MAXN][18];
ll ans=0;
ll gcd(ll a,ll b){return (b==0)?a:gcd(b,a%b);}
ll query(int l,int r)
{
int op=floor(log(r-l+1)/log(2));
int len=1<<op;
return gcd(st[l][op],st[r-len+1][op]);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
int h=floor(log(n)/log(2));
ans=0,memset(st,0,sizeof(0));
for(int i=1;i<=n;i++) scanf("%lld",&st[i][0]);
for(int i=1;i<=h;i++)
{
int len=1<<i;
for(int l=1;l<=(n-len+1);l++)
{
st[l][i]=gcd(st[l][i-1],st[l+(len>>1)][i-1]);
}
}
for(int l=1;l<=n;l++)
{
for(int r=l;r<=n;r++)
{
ll now=query(l,r);
int le=1,rr=n-r+1,mid;
while(le<rr)
{
mid=(le+rr)>>1;
if(query(l,r-1+mid)==now) le=mid+1;
else rr=mid;
}
if(query(l,r-1+le)!=now) le--;
ans=max(ans,now*(ll)(r+le-l));
r=le+r-1;
}
}
printf("%lld\n",ans);
}
return 0;
}
\(134.\) CF888D Almost Identity Permutations
这个比较容易看出是一个有关错排的问题,记住错排递推公式:
容易发现此题答案是 :
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 1005
#define read(x) scanf("%d",&x)
long long c[MAXN][MAXN];
int D[10],n,k;
long long ans=0;
int main()
{
read(n),read(k);
for(int i=0;i<=n;i++) c[i][0]=1ll;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
}
D[0]=1,D[1]=0,D[2]=1,D[3]=2,D[4]=9;
for(int i=0;i<=k;i++)
{
ans+=(long long)c[n][i]*(long long)D[i];
}
printf("%lld\n",ans);
return 0;
}
感 谢 C C F 放 送,超 级 大 模 拟。
不过部分分和数据都很良心的说。
直接写就完了,这里是无脑二分,时间复杂度是 \(\mathcal O(Q\log r)\) 。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<cstring>
#include<ctime>
#define ll long long
#define N number_
#define M number_
using namespace std;
#define MAXN 5000005
#define read(x) scanf("%d",&x)
int q;
ll x;
int lsd,lsm,lsy;
struct node
{
int y,m,d;
}ans[MAXN];
int main()
{
int d=1,m=1,y=-4713;
ans[0].d=1,ans[0].m=1,ans[0].y=-4713;
for(int i=1;i<=1721424;i++)
{
d++;
if(m==1||m==3||m==5||m==7||m==8||m==10||m==12)
{
if(d==32) d=1,m++;
}
else if(m!=2)
{
if(d==31) d=1,m++;
}
else
{
if((-y)%4==1&&d==30)
{
d=1,m=3;
}
else if((-y)%4!=1&&d==29)
{
d=1,m=3;
}
}
if(m>12) m=1,d=1,y++;
ans[i].d=d,ans[i].y=y,ans[i].m=m;
}
ans[1721424].y=1;
y=1,d=1,m=1;
for(int i=1721425;i<=2299160;i++)
{
d++;
if(m==1||m==3||m==5||m==7||m==8||m==10||m==12)
{
if(d==32) d=1,m++;
}
else if(m!=2)
{
if(d==31) d=1,m++;
}
else
{
if(y%4==0&&d==30) d=1,m=3;
else if(y%4!=0&&d==29) d=1,m=3;
}
if(m>12) m=1,d=1,y++;
ans[i].d=d,ans[i].y=y,ans[i].m=m;
}
ans[2299161].d=15,ans[2299161].y=1582,ans[2299161].m=10;
d=15,m=10,y=1582;
for(int i=2299162;i<=2305813;i++)
{
d++;
if(m==1||m==3||m==5||m==7||m==8||m==10||m==12)
{
if(d==32) d=1,m++;
}
else if(m!=2)
{
if(d==31) d=1,m++;
}
else if(m==2)
{
if(y%4!=0)
{
if(d==29) d=1,m=3;
}
else
{
if(y%100!=0)
{
if(d==30) d=1,m=3;
}
else
{
if(y%400!=0)
{
if(d==29) d=1,m=3;
}
else
{
if(d==30) d=1,m=3;
}
}
}
}
if(m>12) m=1,d=1,y++;
ans[i].d=d,ans[i].y=y,ans[i].m=m;
}
read(q);
for(int i=1;i<=q;i++)
{
scanf("%lld",&x);
if(x<=2305813)
{
printf("%d %d ",ans[x].d,ans[x].m);
if(ans[x].y<0)
{
printf("%d BC\n",-ans[x].y);
}
else printf("%d\n",ans[x].y);
}
else
{
x-=2305813;
int l=1601,r=1000000001,mid;
ll dd;
while(l<r)
{
mid=(l+r)>>1;
int fe=(mid-1601);
int op=(fe/4)-(fe/100)+(fe/400);
dd=(1ll*op*366ll+1ll*(fe-op)*365ll);
if(dd>=x) r=mid;
else l=mid+1;
}
int fe=(l-1601);
int op=(fe/4)-(fe/100)+(fe/400);
dd=(1ll*op*366ll+1ll*(fe-op)*365ll);
if(dd>=x) l--;
int f=0;
m=0,d=0;
if(l%400==0||(l%4==0&&l%100!=0)) f=1;
fe=(l-1601);
op=(fe/4)-(fe/100)+(fe/400);
dd=(1ll*op*366ll+1ll*(fe-op)*365ll);
int now=0,df=x-dd;
for(int j=1;j<=12;j++)
{
if(j==1||j==3||j==5||j==7||j==8||j==10||j==12)
{
now+=31;
if(df<=now)
{
df=df-now+31;
m=j;
break;
}
}
else if(j!=2)
{
now+=30;
if(df<=now)
{
df=df-now+30;
m=j;
break;
}
}
else
{
if(f) now+=29;
else now+=28;
if(df<=now)
{
if(f) df=df-now+29;
else df=df-now+28;
m=j;
break;
}
}
}
for(int i=1;;i++)
{
df--;
if(!df)
{
d=i;
break;
}
}
printf("%d %d %d\n",d,m,l);
}
}
return 0;
}
CSP 的签到题,估计就我 CE 了。
细节很多,比如说特判,左移 \(64\)位分开处理都很神仙,时间复杂度是 \(\mathcal O(nk+m)\)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<cstring>
#include<ctime>
#define ll long long
#define dd double
#define N number_
#define M number_
using namespace std;
#define MAXN 1000005
#define ull unsigned long long
#define read(x) scanf("%d",&x)
int n,m,c,k;
ull a[MAXN];
int cnt[100],vis[100];
int p,q,tot;
int main()
{
//freopen("zoo.in","r",stdin);
//freopen("zoo.out","w",stdout);
read(n),read(m),read(c),read(k),tot=k;
if(!n&&!m&&k==64) return puts("18446744073709551616"),0;
for(int i=1;i<=n;i++)
{
scanf("%llu",&a[i]);
for(int j=k-1;j>=0;j--)
{
if(a[i]&(1ull<<j)) cnt[j]++;
}
}
for(int i=1;i<=m;i++)
{
read(p),read(q);
if(!cnt[p]&&!vis[p])
{
tot--;
vis[p]=1;
}
}
if(tot==64) printf("%llu\n",(1ull<<63)-(ull)n+(1ull<<63));
else printf("%llu\n",(1ull<<tot)-(ull)n);
return 0;
}
\(137.\) P3130 [USACO15DEC]haybalesCounting Haybale P
线段树上二分模板题,时间复杂度是 \(\mathcal O(m\log n)\),没有思维难度。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 200005
#define ll long long
#define read(x) scanf("%d",&x)
struct node
{
int l,r;
ll sum,minx,lazy;
}a[MAXN<<2];
int n,m,l,r,x;
int t[MAXN];
char ty[3];
void update(int k)
{
a[k].sum=a[k<<1].sum+a[k<<1|1].sum;
a[k].minx=min(a[k<<1].minx,a[k<<1|1].minx);
}
void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].sum=a[k].minx=(ll)t[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
}
void lazydown(int k)
{
if(a[k].l==a[k].r){a[k].lazy=0;return;}
a[k<<1].minx+=a[k].lazy,a[k<<1|1].minx+=a[k].lazy;
a[k<<1].sum+=(a[k<<1].r-a[k<<1].l+1)*1ll*a[k].lazy;
a[k<<1|1].sum+=(a[k<<1|1].r-a[k<<1|1].l+1)*1ll*a[k].lazy;
a[k<<1].lazy+=a[k].lazy,a[k<<1|1].lazy+=a[k].lazy;
a[k].lazy=0;
}
void modify(int k,int l,int r,ll c)
{
if(a[k].l==l&&a[k].r==r)
{
a[k].sum+=(a[k].r-a[k].l+1)*1ll*c;
a[k].minx+=c;
a[k].lazy+=c;
return;
}
if(a[k].lazy) lazydown(k);
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) modify(k<<1,l,r,c);
else if(l>mid) modify(k<<1|1,l,r,c);
else modify(k<<1,l,mid,c),modify(k<<1|1,mid+1,r,c);
update(k);
}
ll Squery(int k,int l,int r)
{
if(a[k].l==l&&a[k].r==r) return a[k].sum;
if(a[k].lazy) lazydown(k);
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return Squery(k<<1,l,r);
else if(l>mid) return Squery(k<<1|1,l,r);
else return Squery(k<<1,l,mid)+Squery(k<<1|1,mid+1,r);
}
ll Mquery(int k,int l,int r)
{
if(a[k].l==l&&a[k].r==r) return a[k].minx;
if(a[k].lazy) lazydown(k);
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return Mquery(k<<1,l,r);
else if(l>mid) return Mquery(k<<1|1,l,r);
else return min(Mquery(k<<1,l,mid),Mquery(k<<1|1,mid+1,r));
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(t[i]);
build(1,1,n);
for(int i=1;i<=m;i++)
{
scanf("%s",ty),read(l),read(r);
if(ty[0]=='M') printf("%lld\n",Mquery(1,l,r));
else if(ty[0]=='S') printf("%lld\n",Squery(1,l,r));
else read(x),modify(1,l,r,(ll)x);
}
return 0;
}
这个玩意好像挺经典的。
我们发现 \(t<n\) 时,递推式是一个倒金字塔的样子,容易得到:
\(a_{i,j}\) 代表着 \(i\) 时刻 \(j\) 位置的值,这时我们容易想到先把数组平移一下,让第 \(k\) 位到第一位去。
然后我们拆一下式子发现当层数 \(p\) 为 \(2\) 整数次幂时,满足
然后就可以 \(\mathcal O(n\log n)\) 解决这个问题了。
注意空间不太够,所以滚动数组一下就可以了。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 3000000
#define read(x) scanf("%d",&x)
int n,t,k;
int a[MAXN][2];
int f=0;
int main()
{
read(n),read(t),read(k);
for(int i=1;i<=n;i++) read(a[i][0]);
for(int i=k;i<=n;i++) a[i-k+1][1]=a[i][0];
for(int i=1;i<k;i++) a[n-k+1+i][1]=a[i][0];
if(t==1) return printf("%d\n",a[1][1]^a[2][1]);
for(int i=19;i>=0;i--)
{
int rt=1<<i;
if(rt>t) continue;
for(int j=1;j<=n-rt;j++) a[j][f]=a[j][f^1]^a[j+rt][f^1];
f^=1,t-=rt;
}
printf("%d\n",a[1][f^1]);
return 0;
}
\(139.\) P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并
线段树合并模板题,可以类似树上差分的来维护合并,当然还有一种两个 \(\log\) 的神仙做法,不太会。
线段树合并看起来很暴力,但出于修改的限制,时间复杂度是严格的 \(\mathcal O(m\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
#define read(x) scanf("%d",&x)
int n,m,u,v,c;
int dep[MAXN],top[MAXN],f[MAXN],son[MAXN],tot[MAXN];
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
struct Tree
{
int ls,rs,maxn,pos;
}a[MAXN*55];
int opt=0,root[MAXN];
int ans[MAXN];
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
int dfs1(int cur,int fa)
{
f[cur]=fa,dep[cur]=dep[fa]+1,tot[cur]=1;
int maxn=0;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
int op=dfs1(j,cur);
tot[cur]+=op;
if(maxn<op) maxn=op,son[cur]=j;
}
return tot[cur];
}
void dfs2(int cur,int topf)
{
top[cur]=topf;
if(son[cur]) dfs2(son[cur],topf);
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==f[cur]||j==son[cur]) continue;
dfs2(j,j);
}
}
int LCA(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=f[top[u]];
}
return (dep[u]<dep[v])?u:v;
}
void update(int k)
{
if(a[a[k].ls].maxn>=a[a[k].rs].maxn)
a[k].maxn=a[a[k].ls].maxn,a[k].pos=a[a[k].ls].pos;
else
a[k].maxn=a[a[k].rs].maxn,a[k].pos=a[a[k].rs].pos;
}
int modify(int &k,int l,int r,int x,int y)
{
if(!k) k=++opt;
if(l==r)
{
a[k].maxn+=y,a[k].pos=l;
return k;
}
int mid=(l+r)>>1;
if(x<=mid) a[k].ls=modify(a[k].ls,l,mid,x,y);
else a[k].rs=modify(a[k].rs,mid+1,r,x,y);
update(k);
return k;
}
int merge(int u,int v,int l,int r)
{
if(!u||!v) return u|v;
if(l==r)
{
a[u].maxn+=a[v].maxn;
a[u].pos=l;
return u;
}
int mid=(l+r)>>1;
a[u].ls=merge(a[u].ls,a[v].ls,l,mid);
a[u].rs=merge(a[u].rs,a[v].rs,mid+1,r);
update(u);
return u;
}
void dfs(int cur)
{
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==f[cur]) continue;
dfs(j);
root[cur]=merge(root[cur],root[j],1,100000);
}
if(a[root[cur]].maxn) ans[cur]=a[root[cur]].pos;
}
int main()
{
read(n),read(m);
for(int i=1;i<n;i++) read(u),read(v),add(u,v),add(v,u);
dfs1(1,1),dfs2(1,1);
for(int i=1;i<=m;i++)
{
read(u),read(v),read(c);
int now=LCA(u,v);
root[u]=modify(root[u],1,100000,c,1);
root[v]=modify(root[v],1,100000,c,1);
root[now]=modify(root[now],1,100000,c,-1);
if(f[now]^now) root[f[now]]=modify(root[f[now]],1,100000,c,-1);
}
dfs(1);
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}
bug 题,错误思路反向建边就能过,不知道为什么,思路就是复合加权的最短路问题了。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define read(x) scanf("%lld",&x)
#define int long long
int n,m;
int u,v,w;
struct node
{
int to,nxt,w;
}e[200005];
int head[10005],cnt=0;
int dis[10005],len[10005],vis[10005],pre[10005];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
void add(int u,int v,int w){e[++cnt].nxt=head[u],e[cnt].to=v,e[cnt].w=w,head[u]=cnt;}
void dij()
{
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+len[u]+1ll+e[i].w)
{
dis[j]=dis[u]+len[u]+1ll+e[i].w;
len[j]=len[u]+1ll,pre[j]=u;
q.push(make_pair(dis[j],j));
}
}
}
return;
}
signed main()
{
read(n),read(m);
for(int i=1;i<=m;i++) read(u),read(v),read(w),add(v,u,w);
for(int i=1;i<=n;i++) dis[i]=1ll<<62,len[i]=-1ll;
dis[n]=0,q.push(make_pair(0,n));
dij();
printf("%lld\n",dis[1]);
int p=1;
while(p)
{
printf("%lld ",p);
p=pre[p];
}
return puts(""),0;
}
\(141.\) P3521 [POI2011]ROT-Tree Rotations
线段树合并练习题。
考虑按树形结构合并,容易想到只有两种方法,这时候分别维护左右儿子谁在前的的逆序对新加数,具体就是左右子树大小之积。
时间复杂度是 \(\mathcal O(n\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 200005
#define ll long long
#define read(x) scanf("%d",&x)
int n,p;
struct node
{
int ls,rs,sum;
node(){ls=rs=sum=0;}
}a[MAXN*25];
int cnt=0,op=0,root[MAXN];
ll ans=0,now1=0,now2=0;
inline void update(int k){a[k].sum=a[a[k].ls].sum+a[a[k].rs].sum;}
int build(int k,int l,int r,int x)
{
if(!k) k=++cnt;
if(l==r){a[k].sum=1;return k;}
int mid=(l+r)>>1;
if(x<=mid) a[k].ls=build(a[k].ls,l,mid,x);
else a[k].rs=build(a[k].rs,mid+1,r,x);
update(k);
return k;
}
int merge(int u,int v,int l,int r)
{
if(!u||!v) return u|v;
if(l==r){a[u].sum+=a[v].sum;return u;}
int mid=(l+r)>>1;
now1+=1ll*a[a[u].ls].sum*a[a[v].rs].sum;
now2+=1ll*a[a[v].ls].sum*a[a[u].rs].sum;
a[u].ls=merge(a[u].ls,a[v].ls,l,mid);
a[u].rs=merge(a[u].rs,a[v].rs,mid+1,r);
update(u);
return u;
}
int work()
{
read(p);
int er=p;
if(er)
{
op++;
root[op]=build(root[op],1,n,er);
return op;
}
else
{
int lso=work();
int rso=work();
root[lso]=merge(root[lso],root[rso],1,n);
ans+=min(now1,now2);
now1=now2=0;
return lso;
}
}
int main()
{
read(n);
work();
return printf("%lld\n",ans),0;
}
对每个节点分别开权值线段树,然后进行合并即可。
时间复杂度是 \(\mathcal O(n\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
#define read(x) scanf("%lld",&x)
#define int long long
int n,u,v;
struct edge
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
struct node
{
int ls,rs,sum,maxn;
}a[MAXN*25];
int opt=0,rt[MAXN],ans[MAXN];
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void update(int k)
{
if(a[a[k].ls].maxn==a[a[k].rs].maxn)
a[k].maxn=a[a[k].ls].maxn,a[k].sum=a[a[k].ls].sum+a[a[k].rs].sum;
else if(a[a[k].ls].maxn>a[a[k].rs].maxn)
a[k].maxn=a[a[k].ls].maxn,a[k].sum=a[a[k].ls].sum;
else
a[k].maxn=a[a[k].rs].maxn,a[k].sum=a[a[k].rs].sum;
}
int build(int k,int l,int r,int x)
{
if(!k) k=++opt;
if(l==r)
{
a[k].sum=x,a[k].maxn=1;
return k;
}
int mid=(l+r)>>1;
if(x<=mid) a[k].ls=build(a[k].ls,l,mid,x);
else a[k].rs=build(a[k].rs,mid+1,r,x);
return update(k),k;
}
int merge(int u,int v,int l,int r)
{
if(!u||!v) return u|v;
if(l==r)
{
a[u].maxn+=a[v].maxn;
return u;
}
int mid=(l+r)>>1;
a[u].ls=merge(a[u].ls,a[v].ls,l,mid);
a[u].rs=merge(a[u].rs,a[v].rs,mid+1,r);
return update(u),u;
}
void dfs(int cur,int fa)
{
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
dfs(j,cur);
rt[cur]=merge(rt[cur],rt[j],1,100000);
}
ans[cur]=a[rt[cur]].sum;
}
signed main()
{
read(n);
for(int i=1;i<=n;i++) read(u),rt[i]=build(rt[i],1,100000,u);
for(int i=1;i<n;i++)
{
read(u),read(v);
add(u,v),add(v,u);
}
dfs(1,1);
for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
return puts(""),0;
}
\(142.\) P3434 [POI2006]KRA-The Disks
果然是数据结构中毒选手,果断线段树上二分。
时间复杂度是 \(\mathcal O(m\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 300005
#define read(x) scanf("%d",&x)
int n,m;
struct node
{
int l,r,minx;
}a[MAXN<<2];
int t[MAXN],x,R;
inline void update(int k){a[k].minx=min(a[k<<1].minx,a[k<<1|1].minx);}
inline void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].minx=t[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
}
inline int query(int k,int l,int r,int x)
{
if(a[k].l==a[k].r)
{
if(a[k].l==1&&x>a[k].minx) return 0;
return a[k].l;
}
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return query(k<<1,l,r,x);
else if(a[k<<1].minx<x) return query(k<<1,l,mid,x);
else return query(k<<1|1,mid+1,r,x);
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(t[i]);
build(1,1,n),R=n;
for(int i=1;i<=m;i++)
{
read(x);
if(R<0) continue;
R=query(1,1,R,x)-1;
}
if(R<0) puts("0");
else printf("%d\n",R);
return 0;
}
具体见这里:CSP 2020 S 题解
这里只放一下代码:
#include"cstring"
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define MAXN 1000005
#define int long long
#define read(x) scanf("%d",&x)
int t,n,m,x,y;
int a[MAXN],b[MAXN],tt[MAXN],vis[MAXN];
int l,r;
int head=0,tail=1;
struct node
{
int val,id;
}q[MAXN*2];
int maxn,minx,minxx;
int cnt,tot;
int num1,num2;
signed main()
{
read(t),t--;
read(n),l=1,r=n,cnt=n;
for(int i=1;i<=n;i++) read(a[i]),b[i]=i,tt[i]=a[i];
while(1)
{
if(head>=tail)
{
if(q[tail].val>a[r]||(q[tail].val==a[r]&&q[tail].id>b[r]))
num1=q[tail].id,maxn=q[tail++].val;
else num1=b[r],maxn=a[r--];
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
minx=q[head--].val;
else minx=a[l++];
}
else num1=b[r],maxn=a[r--],minx=a[l++];
cnt-=2;
if(!cnt)
{
puts("1");
break;
}
if(head>=tail)
{
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
num2=q[head].id,minxx=q[head].val;
else num2=b[l],minxx=a[l];
}
else num2=b[l],minxx=a[l];
if(maxn-minx<minxx||(maxn-minx==minxx&&num1<num2))
{
tot=cnt+1;
a[--l]=maxn-minx,b[l]=num1;
int op=0;
while(1)
{
if(head>=tail)
{
if(q[tail].val>a[r]||(q[tail].val==a[r]&&q[tail].id>b[r]))
num1=q[tail].id,maxn=q[tail++].val;
else num1=b[r],maxn=a[r--];
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
minx=q[head--].val;
else minx=a[l++];
}
else num1=b[r],maxn=a[r--],minx=a[l++];
tot-=2;
if(!tot)
{
if(op%2==1) cnt--;
break;
}
if(head>=tail)
{
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
num2=q[head].id,minxx=q[head].val;
else num2=b[l],minxx=a[l];
}
else num2=b[l],minxx=a[l];
if(maxn-minx>minxx||(maxn-minx==minxx&&num1>num2))
{
if(op%2==1) cnt--;
break;
}
op++,tot++;
a[--l]=maxn-minx,b[l]=num1;
}
printf("%d\n",cnt+2);
break;
}
else if(maxn-minx>a[r]||(maxn-minx==a[r]&&num1>b[r]))
{
a[++r]=maxn-minx;
b[r]=num1;
}
else q[++head].val=maxn-minx,q[head].id=num1;
cnt++;
}
while(t--)
{
l=1,r=n,cnt=n;
head=0,tail=1;
memset(q,0,sizeof(q));
memset(vis,0,sizeof(vis));
read(m);
for(int i=1;i<=m;i++)
{
read(x),read(y);
a[x]=y,vis[x]=1;
}
for(int i=1;i<=n;i++)
{
b[i]=i;
if(!vis[i]) a[i]=tt[i];
tt[i]=a[i];
}
while(1)
{
if(head>=tail)
{
if(q[tail].val>a[r]||(q[tail].val==a[r]&&q[tail].id>b[r]))
num1=q[tail].id,maxn=q[tail++].val;
else num1=b[r],maxn=a[r--];
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
minx=q[head--].val;
else minx=a[l++];
}
else num1=b[r],maxn=a[r--],minx=a[l++];
cnt-=2;
if(!cnt)
{
puts("1");
break;
}
if(head>=tail)
{
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
num2=q[head].id,minxx=q[head].val;
else num2=b[l],minxx=a[l];
}
else num2=b[l],minxx=a[l];
if(maxn-minx<minxx||(maxn-minx==minxx&&num1<num2))
{
tot=cnt+1;
a[--l]=maxn-minx,b[l]=num1;
int op=0;
while(1)
{
if(head>=tail)
{
if(q[tail].val>a[r]||(q[tail].val==a[r]&&q[tail].id>b[r]))
num1=q[tail].id,maxn=q[tail++].val;
else num1=b[r],maxn=a[r--];
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
minx=q[head--].val;
else minx=a[l++];
}
else num1=b[r],maxn=a[r--],minx=a[l++];
tot-=2;
if(!tot)
{
if(op%2==1) cnt--;
break;
}
if(head>=tail)
{
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
num2=q[head].id,minxx=q[head].val;
else num2=b[l],minxx=a[l];
}
else num2=b[l],minxx=a[l];
if(maxn-minx>minxx||(maxn-minx==minxx&&num1>num2))
{
if(op%2==1) cnt--;
break;
}
op++,tot++;
a[--l]=maxn-minx,b[l]=num1;
}
printf("%d\n",cnt+2);
break;
}
else if(maxn-minx>a[r]||(maxn-minx==a[r]&&num1>b[r]))
{
a[++r]=maxn-minx;
b[r]=num1;
}
else q[++head].val=maxn-minx,q[head].id=num1;
cnt++;
}
}
return 0;
}
还是看哪个题解吧,,,,
#include"iostream"
#include"cstdio"
#include"queue"
using namespace std;
#define int long long
#define read(x) scanf("%lld",&x)
#define MAXN 100005
#define MOD 998244353
int n,m,q;
int c,x,a[MAXN];
struct node
{
int to,nxt;
}e[MAXN*10];
int head[MAXN],cnt=0;
int mul[MAXN],f[MAXN];
int ty[MAXN],pos[MAXN],fac[MAXN];
int b[MAXN],deg[MAXN],dp[MAXN],ad[MAXN];
int vis[MAXN];
queue<int>qu;
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void dfs(int cur)
{
vis[cur]=mul[cur]=1ll;
if(ty[cur]==2) mul[cur]=fac[cur];
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(!vis[j]) dfs(j);
mul[cur]=mul[cur]*mul[j]%MOD;
}
return;
}
signed main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i]);
read(m);
for(int i=1;i<=m;i++)
{
read(ty[i]);
if(ty[i]==1) read(pos[i]),read(fac[i]);
else if(ty[i]==2) read(fac[i]);
else
{
read(c);
for(int j=1;j<=c;j++) read(x),add(i,x),deg[x]++;
}
}
for(int i=1;i<=m;i++) if(!vis[i]) dfs(i);
read(q);
for(int i=1;i<=q;i++) read(b[i]);
f[q+1]=1ll,dp[b[q]]=1ll;
for(int i=q;i>=1;i--)
{
f[i]=f[i+1]*mul[b[i]]%MOD;
dp[b[i-1]]=(dp[b[i-1]]+f[i])%MOD;
}
for(int i=1;i<=m;i++) if(!deg[i]) qu.push(i);
while(!qu.empty())
{
int u=qu.front();
qu.pop();
if(ty[u]==1) ad[pos[u]]=(ad[pos[u]]+fac[u]*dp[u]%MOD)%MOD;
int mll=1ll;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
dp[j]=(dp[j]+mll*dp[u]%MOD)%MOD;
mll=mll*mul[j]%MOD;
deg[j]--;
if(!deg[j]) qu.push(j);
}
}
for(int i=1;i<=n;i++)
{
a[i]=a[i]*f[1]%MOD+ad[i];
printf("%lld ",a[i]%MOD);
}
return puts(""),0;
}
环形均分纸牌问题。
考虑记 \(x_i\) 是 \(i\sim i+1\) 的数量,特殊的,\(x_n\) 为 \(n\sim 1\) 的。
容易有:
把所有的数都用 \(x_1\) 表示,最后求 \(\sum\limits_{i=1}^n |x_i|\) 就只剩一一个参数了,然后中位数定理即可。
实现的好的话应该可以 \(\mathcal O(n)\),我比较懒,所以写的是好写的 \(\mathcal O(n\log n)\)。
#include"algorithm"
#include"iostream"
#include"cstdio"
using namespace std;
#define ll long long
#define MAXN 5000005
int n;
ll a[MAXN],s[MAXN],c[MAXN],p[MAXN];
ll x[MAXN];
ll sum=0,now;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum+=a[i];
sum=sum/(ll)n;
for(int i=1;i<=n;i++) s[i]=sum-a[i];
for(int i=2;i<=n;i++) c[i]=c[i-1]+s[i],p[i]=c[i];
sort(c+1,c+n+1),sum=0;
now=c[(n+1)/2];
for(int i=1;i<=n;i++) sum=sum+abs(now-p[i]);
printf("%lld\n",sum);
for(int i=1;i<=n;i++) x[i]=now-p[i];
printf("%lld %lld\n",-x[n],x[1]);
for(int i=2;i<=n;i++) printf("%lld %lld\n",-x[i-1],x[i]);
return 0;
}
双倍经验啊,,,
#include"algorithm"
#include"iostream"
#include"cstdio"
using namespace std;
#define ll long long
#define MAXN 5000005
int n;
ll a[MAXN],s[MAXN],c[MAXN],p[MAXN];
ll x[MAXN];
ll sum=0,now;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum+=a[i];
sum=sum/(ll)n;
for(int i=1;i<=n;i++) s[i]=sum-a[i];
for(int i=2;i<=n;i++) c[i]=c[i-1]+s[i],p[i]=c[i];
sort(c+1,c+n+1),sum=0;
now=c[(n+1)/2];
for(int i=1;i<=n;i++) sum=sum+abs(now-p[i]);
printf("%lld\n",sum);
return 0;
}
我们设设 \(dp_{i,j}\) 表示考虑前 \(i\) 个物品,当前剩下不少于 \(j\) 个空挂钩时挂饰总和的最大值是多少。
于是有 :
然后按提供的挂钩数排序即可。
时间复杂度是 \(\mathcal O(n^2)\).
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define int long long
#define MAXN 2005
#define read(x) scanf("%lld",&x)
#define inf 1ll<<61
int n;
struct node
{
int a,b;
}x[MAXN];
int dp[MAXN][MAXN];
int ans=0;
bool cmp(node n,node m){return n.a>m.a;}
signed main()
{
read(n);
for(int i=1;i<=n;i++) read(x[i].a),read(x[i].b);
sort(x+1,x+n+1,cmp);
for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) dp[i][j]=-(inf<<1);
dp[0][1]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j+1-x[i].a<=n&&j<=n;j++)
{
if(dp[i-1][j]>-inf) dp[i][j]=dp[i-1][j];
if(dp[i-1][j+1-x[i].a]>-inf)
dp[i][j]=max(dp[i][j],dp[i-1][max(0ll,j-x[i].a)+1]+x[i].b);
ans=max(ans,dp[i][j]);
}
}
printf("%lld\n",ans);
return 0;
}
\(148.\) P4085 [USACO17DEC]Haybale Feast G
二分这个阈值,枚举左端点,发现再二分一次右端点就行了。
第二次二分发现可以线段树,线段树 nm,前缀和维护不就好了/jk
考虑优化,我们发现可以先处理出值大于这个阈值的位置,然后这个序列就被分成 \(k\) 个区间,扫一遍取最值就好了,复杂度是 \(\mathcal O(n\log n)\)。
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
#define ll long long
int n;
ll m;
int f,s[MAXN];
ll sum[MAXN];
int l=1000000000,r;
int op[MAXN],cnt=0;
#define g() getchar()
inline int read()
{
int x=0;
char c=g();
while(c<'0'||c>'9') c=g();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=g();
return x;
}
inline bool check(int x)
{
memset(op,0,sizeof(op)),cnt=0;
for(int i=1;i<=n;i++) if(s[i]>x) op[++cnt]=i;
op[++cnt]=n+1;
for(register int i=0;i<cnt;i++)
{
if(op[i+1]==op[i]+1) continue;
if(sum[op[i+1]-1]-sum[op[i]]>=m) return true;
}
return false;
}
int main()
{
n=read(),scanf("%lld",&m);
for(register int i=1;i<=n;i++)
{
f=read(),s[i]=read();
sum[i]=sum[i-1]+(ll)f,r=max(r,s[i]),l=min(l,s[i]);
}
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}
\(149.\) P4799 [CEOI2015 Day2]世界冰球锦标赛
折半搜索(Meet in Middle) 板子题,预处理一部分,然后再算另一部分,二分匹配即可。
时间复杂度是 \(\mathcal O(2^{\frac{n}{2}+1}n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
#include"algorithm"
#include"stack"
#include"queue"
using namespace std;
#define read(x) scanf("%d",&x)
#define readl(x) scanf("%lld",&x)
#define ll long long
#define ull unsigned long long
#define MOD 1000000007
int n;
ll a[45],ans=0,m;
ll val[2000005],top=0;
int find(ll x)//注意 long long
{
int l=1,r=top;
while(l<r)
{
int mid=(l+r)>>1;
if(val[mid]>x) r=mid;
else l=mid+1;
}
if(val[l]>x) l--;
return l;
}
int main()
{
read(n),readl(m);
for(int i=1;i<=n;i++) readl(a[i]);
int mid=n/2,rt=n-mid;
for(int s=0;s<(1<<rt);s++)
{
ll op=0;
for(int i=0;i<rt;i++)
{
if((1<<i)&s) op+=a[mid+i+1];
}
val[++top]=op;
}
sort(val+1,val+top+1);
for(int s=0;s<(1<<mid);s++)
{
ll op=0;
for(int i=0;i<mid;i++)
{
if((1<<i)&s) op+=a[i+1];
}
if(op<=m) ans+=(ll)find(m-op);
}
printf("%lld\n",ans);
return 0;
}
线段树合并,并查集辅助维护,这是套路题啊(
时间复杂度是 \(\mathcal O((n+m+q)\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
#include"algorithm"
#include"stack"
#include"queue"
using namespace std;
#define read(x) scanf("%d",&x)
#define readl(x) scanf("%lld",&x)
#define ll long long
#define ull unsigned long long
#define MAXN 100005
#define MOD 1000000007
int n,m,q,cnt=0;
struct node
{
int ls,rs,sum,pos;
node(){ls=rs=sum=pos;}
}a[MAXN*28];
int fa[MAXN],x,y,op,root[MAXN],si[MAXN];
char c[2];
int getf(int u){return (fa[u]==u)?u:fa[u]=getf(fa[u]);}
void update(int k){a[k].sum=a[a[k].ls].sum+a[a[k].rs].sum;}
int build(int k,int l,int r,int x,int t)
{
if(!k) k=++cnt;
if(l==r)
{
a[k].sum=1,a[k].pos=t;
return k;
}
int mid=(l+r)>>1;
if(x<=mid) a[k].ls=build(a[k].ls,l,mid,x,t);
else a[k].rs=build(a[k].rs,mid+1,r,x,t);
return update(k),k;
}
int merge(int u,int v,int l,int r)
{
if(!u||!v) return u|v;
if(l==r)
{
a[u].sum+=a[v].sum,a[u].pos=a[v].pos|a[u].pos;
return u;
}
int mid=(l+r)>>1;
a[u].ls=merge(a[u].ls,a[v].ls,l,mid);
a[u].rs=merge(a[u].rs,a[v].rs,mid+1,r);
return update(u),u;
}
int query(int k,int l,int r,int x)
{
if(l==r) return a[k].pos;
int midd=a[a[k].ls].sum,mid=(l+r)>>1;
if(x<=midd) return query(a[k].ls,l,mid,x);
else return query(a[k].rs,mid+1,r,x-midd);
}
void att(int u,int v)
{
int l=getf(u),r=getf(v);
if(l==r) return;
else
{
if(si[r]>si[l]) swap(l,r);
fa[r]=l,si[l]+=si[r];
root[l]=merge(root[l],root[r],1,n);
}
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) fa[i]=i,si[i]=1,read(x),root[i]=build(root[i],1,n,x,i);
for(int i=1;i<=m;i++)
{
read(x),read(y);
att(x,y);
}
read(q);
for(int i=1;i<=q;i++)
{
scanf("%s",c),read(x),read(y);
if(c[0]=='Q')
{
x=getf(x);
if(a[root[x]].sum<y) puts("-1");
else printf("%d\n",query(root[x],1,n,y));
}
else att(x,y);
}
return 0;
}