……

口胡(然而有代码)<第三章>

他来了!

估计是最后一章了/kk。(诶,能去 NOIP 好像可以续命啊)

上期精彩:口胡(然而有代码)<第二章>

题目计数:\(150\)

\(101.\) P2659 美丽的序列

提供一种比较屑的 \(\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\) 的部分。

显然可以得到:

\[f_{i,1,0}=\max\{f_{i-1,1,0}+a_{2i},f_{i-1,0,0}+\max\{a_{2i-1},a_{2i}\},f_{i-1,0,1}+a_{2i}\} \]

\[f_{i,1,1}=a_{2i+1}+\max\{f_{i-1,1,0},f_{i-1,1,1},f_{i-1,0,1},f_{i-1,0,0}\} \]

最后答案是 \(\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;
}

\(104.\) P3408 恋爱

在每个节点维护小根堆,然后暴力转移即可。

没有代码,复杂度是 \(\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,就做完了,反正很套路...


\(109.\) P4551 最长异或路径

可以当 \(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;
}

\(110.\) P1944 最长括号匹配

考虑压栈,然后不断把上一个和这一个中间的长度给加上。

然后更新一下从这个位置开始最远到哪里,于是就能实现 \(\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\) 时,人一定够用。

考虑期望的定义,在这个题中,容易知道;

\[E(x)=\sum_{i=1}^t i\dbinom{t}{i}p^{i}(1-p)^{t-i} \]

考虑组合数的一个性质

\[\dbinom{t}{i}i=\dfrac{t!\times i}{(t-i)!\times i!}=t\dfrac{(t-1)!}{(t-1-(i-1))!\times(i-1!)}=t\dbinom{t-1}{i-1} \]

考虑将式子枚举项改变:

\[E(x)=t\sum_{i=0}^{t-1}\dbinom{t-1}{i}p^{i}(1-p)^{t-1-i} \]

我们发现提出一个 \(p\) 就能构造二项式定理:

于是有:

\[E(x)=tp(p+1-p)^{t-1}=tp \]

故直接输出 \(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;
}

\(112.\) P1169 [ZJOI2007]棋盘制作

悬线法,从前学过没写过,故来补一补。

可以做到 \(\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;
}

\(113.\) P4147 玉蟾宫

还是悬线法(

时间复杂度是 \(\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;
}

\(115.\) P4285 [SHOI2008]汉诺塔

构造线性递推式即可,好神仙啊。

当然是预处理出斜率和截距辣!

时间复杂度是 \(\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;
} 

\(116.\) P2700 逐个击破

考虑最小生成树,我们把这些限制点连起来,然后最小生成树就好了,时间复杂度是 \(\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;
}

\(119.\) P1242 新汉诺塔

除了最后一个点特判掉,其他都挺好的......

#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;
}

\(120.\) P3942 将军令

别问我为什么这么想,感觉挺自然的,然后 \(\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;
}

\(121.\) P2168 [NOI2015]荷马史诗

哈夫曼树板子题。

借用合并果子的 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;
}

\(122.\) P1450 [HAOI2008]硬币购物

容斥经典题,背包之后去除多余状态就好。

时间复杂度是 \(\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;
}

\(123.\) P5664 Emiya 家今天的饭

考虑容斥用总方案数减去不合法的方案数就是最终答案。

不合法的方案数比较好求,我们考虑只记录最多的那一列比其他的多多少。

为什么不能直接求合法的呢?

因为我们在计数时,只是粗略的统计,如果所谓最多的那一列其实在统计中已经不是最多的了,那就完蛋了。

可以用 \(\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;
}

\(124.\) P1073 最优贸易

感觉很套路的缩点和 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;
}

\(126.\) P1487 失落的成绩单

递推数列,考虑引进数列上其他一个数为未知变量,递推后最后得到一元一次方程,解出来即可。

时间复杂度是 \(\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;
}

\(130.\) P3938 斐波那契

集训时考到,原题教一下。

代码可能比较难看/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

这个比较容易看出是一个有关错排的问题,记住错排递推公式:

\[D_i=(i-1)(D_{i-1}+D_{i-2}) \]

容易发现此题答案是 :

\[\sum_{i=0}^k \dbinom{n}{i}D_i \]

#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;
}

\(135.\) P7075 儒略日

感 谢 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;
}

\(136.\) P7076 动物园

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;
}

\(138.\) P3937 Changing

这个玩意好像挺经典的。

我们发现 \(t<n\) 时,递推式是一个倒金字塔的样子,容易得到:

\[a_{i,j}=a_{i-1,j}\;\text{xor}\;a_{i-1,j+1} \]

\(a_{i,j}\) 代表着 \(i\) 时刻 \(j\) 位置的值,这时我们容易想到先把数组平移一下,让第 \(k\) 位到第一位去。

然后我们拆一下式子发现当层数 \(p\)\(2\) 整数次幂时,满足

\[a_{i,j}=a_{i-p,j}\;\text{xor}\; a_{i-p,j+p} \]

然后就可以 \(\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;
}

\(140.\) P2832 行路难

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;
}

\(141.\) CF600E Lomsat gelral

对每个节点分别开权值线段树,然后进行合并即可。

时间复杂度是 \(\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;
}

\(143.\) P7078 贪吃蛇

具体见这里: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;
}

\(144.\) P7077 函数调用

还是看哪个题解吧,,,,

#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;
}

\(145.\) P2125 图书馆书架上的书

环形均分纸牌问题。

考虑记 \(x_i\)\(i\sim i+1\) 的数量,特殊的,\(x_n\)\(n\sim 1\) 的。

容易有:

\[x_i=x_{i-1}-(\overline{a}-a_i) \]

把所有的数都用 \(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;
}

\(146.\) P4016 负载平衡问题

双倍经验啊,,,

#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;
}

\(147.\) P4138 [JOISC2014]挂饰

我们设设 \(dp_{i,j}\) 表示考虑前 \(i\) 个物品,当前剩下不少于 \(j\) 个空挂钩时挂饰总和的最大值是多少。

于是有 :

\[dp_{i,j}=\max\{dp_{i-1,j},dp_{i-1,\max\{j-a_i,0\}+1}\} \]

然后按提供的挂钩数排序即可。

时间复杂度是 \(\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;
}

\(150.\) P3224 [HNOI2012]永无乡

线段树合并,并查集辅助维护,这是套路题啊(

时间复杂度是 \(\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;
}

精彩的延续:Link

posted @ 2020-10-30 08:30  童话镇里的星河  阅读(146)  评论(0编辑  收藏  举报