……

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

把 AFO 之前口胡的题放这里,一句话题解。

可能题有点多,影响阅读质量(不过没几个人来看这种屑的文章吧),所以这里只放 \(T_1\sim T_{50}\)

下一章:口胡(然而有代码)<第二章>

题目计数:\(50\)

\(1.\) P4113 [HEOI2012]采花

和 HH的项链一样,维护一下第二个出现的值,然后差分,树状数组即可,时间复杂度 \(\mathcal O(n\log n)\)


\(2.\) P2398 GCD SUM

有公式:

\[\sum\limits_{d|n}\varphi(d)=n \]

推式子:

\[\begin{aligned}\sum\limits_{i=1}^n\sum\limits_{j=1}^n\gcd(i,j)&=\sum\limits_{i=1}^n\sum\limits_{j=1}^n\sum_{d|\gcd(i,j)}\varphi(d)\cr&=\sum\limits_{d=1}^n\varphi(d)\left\lfloor\dfrac{n}{d}\right\rfloor^2\end{aligned} \]

整除分块,时间复杂度 \(\mathcal O(n+\sqrt{n})\)


\(3.\) P1314 聪明的质监员

发现 \(y_i\) 是单调的,求完和还是单调的。

那么二分一个 \(W\),对于序列每次维护一个前缀和,对每个区间差分一下,就能 \(\mathcal O(1)\) 了,时间复杂度 \(\mathcal O((n+m)\log w_i)\)


\(4.\) P6625 [省选联考 2020 B 卷] 卡牌游戏

省选的橙题也是挺有意思的.jpg


首先,题解中的做法我并没有想到。

但着不代表这题不可做,,,

我们考虑什么时候收手合并,当然是负数了!
如果负数后面有个很大的数呢?
如果费力把后面的数纳入现在的卡,那么就会少一次合并机会,得不偿失,所以只要是非负数就合并为一个卡,同时更新 \(sum\)

记录一下合并完的卡片,作为第一张,第二张用一个动态的指针来记录一下,记得判断是否已经到了最后一张卡片,如果是的话并且总和比 \(0\) 大,就加上,还有就是要注意 long long

时间复杂度是 \(\mathcal O(n)\)

\(Code\):

#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 100005 
#define ll long long

int n,a[MAXN];
ll sum=0,fir=0;
ll now=0;
int lst=2;

int main()
{
	read(n);for(int i=1;i<=n;i++) read(a[i]);
	fir=a[1];
	while(lst<=n)
	{
		now=fir+(ll)a[lst];
		if(lst==n&&now>=0) sum+=now;
		lst++;
		int k=lst;
		for(int i=k;i<=n;i++)
		{
			if(now>=0){sum+=now,fir=now;break;}
			now+=a[i],lst++;
			if(i==n&&now>=0) sum+=now;
		}
	}
	printf("%lld\n",sum);
	return 0;
}

\(5.\) P2285 [HNOI2004]打鼹鼠

真就 \(\mathcal O(m^2)\) 啊?

\(dp\) 似乎很显然,并没有蓝题难度。

\(dp_i\) 为最后敲的鼹鼠为第 \(i\) 只鼹鼠时最多的得分,那么:

\[dp_i=\max\limits_{j=1}^{i-1} \{(dp_j+1)×[|x_{a_i}-x_{a_j}|+|y_{a_i}-y_{a_j}|\leqslant \mathit{\Delta} t]\} \]

然后得到所有 \(dp\) 数组中的最大值即可,注意 \(dp_1=1\) ,假设第一个一定能用。

#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;

#define MAXN 10005

int n,m;
struct node
{
	int t,x,y;
}a[MAXN];
int dp[MAXN]={0},ans=1;

inline int read()
{
	int x=0;
	char c=getchar();
	while(c>'9'||c<'0'){c=getchar();}
	while(c<='9'&&c>='0'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x;
}

inline int dis(int l,int r){return abs(a[l].x-a[r].x)+abs(a[l].y-a[r].y);}

int main()
{
	n=read(),m=read();
	for(register int i=1;i<=m;++i) a[i].t=read(),a[i].x=read(),a[i].y=read();
	dp[1]=1;
	for(register int i=2;i<=m;++i)
	{
		for(register int j=m-1;j>=1;--j)
		{
			if(a[i].t-a[j].t>=dis(i,j)) dp[i]=max(dp[i],dp[j]+1),ans=max(ans,dp[i]);
		}
	}
	printf("%d\n",ans);
	return 0;
}

\(C++ 14+O_2\) 就真快啊


\(6.\) UVA11572 唯一的雪花 Unique Snowflakes

紫书上给出了 \(\mathcal O(n\log n)\) 的做法,所以我给出了写法比较麻烦一些的 \(\mathcal O(n)\) 做法。

考虑对每个点维护下一个出现此颜色的位置,并在每个位置记录有是否有前面的点把他标记了。

最后维护一个指针标记一下,如果这个点和他下一个都在一个暂时的最小区间中,就更新最小区间,并不断处理扫过区间的弹出。

这样就能 \(\mathcal O(n)\) 了。


\(7.\) P2508 [HAOI2008]圆上的整点

详解见:Link

珂以看视频解决。


\(8.\) P1967 货车运输

看标签大法好。

求出最大生成树,所有的最优方案都包含在这里面。

然后维护两点之间路径的最小值,显然可以用树剖 \(+\) 线段树或 ST 表维护,但是不会 ST 表了,只能写线段树了/kk。

复杂度 \(\mathcal O(q\log ^2 n)\)

代码:

#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
#include"cstring"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 50005
#define inf 2147483647

int n,m,q;
struct node
{
    int x,y,z;
}ed[MAXN];
struct edge
{
    int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0,c=0;
int f[MAXN],fat[MAXN];
int tot[MAXN],son[MAXN];
int t[MAXN],vis[MAXN];
int cntt=0,topf[MAXN],id[MAXN];
int dis[MAXN],dep[MAXN];
struct Tree
{
    int maxn,l,r;
}a[MAXN<<2];
int x,y;

inline void update(int k){a[k].maxn=min(a[k<<1].maxn,a[k<<1|1].maxn);}

void build(int k,int l,int r)
{
    a[k].l=l,a[k].r=r;
    if(l==r){a[k].maxn=t[l];return;}
    int mid=(l+r)>>1;
    build(k<<1,l,mid),build(k<<1|1,mid+1,r);
    update(k);
    return;
}

int query(int k,int l,int r)
{
    if(l>r) return inf;
    if(a[k].l==l&&a[k].r==r) return a[k].maxn;
    int mid=(a[k].l+a[k].r)>>1;
    if(r<=mid) return query(k<<1,l,r);
    else if(l>mid) return query(k<<1|1,l,r);
    else return min(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}

void add(int u,int v,int w)
{
    e[++cnt].to=v;
    e[cnt].w=w;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}

void init(int n){for(int i=1;i<=n;i++) f[i]=i;}
int getf(int u){return f[u]=(f[u]==u)?u:getf(f[u]);}
bool merge(int u,int v){int t1=getf(u),t2=getf(v);if(t1!=t2){f[t2]=t1;return true;}else return false;}

bool cmp(node n,node m){return n.z>m.z;}

int dfs1(int cur,int fa,int deep)
{
    vis[cur]=1;
    fat[cur]=fa;
    tot[cur]=1;
    dep[cur]=deep;
    int maxson=0;
    for(int i=head[cur];i;i=e[i].nxt)
    {
        int j=e[i].to;
        if(j==fa) continue;
        tot[cur]+=dfs1(j,cur,deep+1);
        if(maxson<tot[j]) maxson=tot[j],son[cur]=j,dis[cur]=e[i].w;
    }
    return tot[cur];
}

void dfs2(int cur,int fa,int top,int lng)
{
	vis[cur]=1;
    topf[cur]=top;
    if(fa!=cur) cntt++,id[cur]=cntt,t[cntt]=lng;
    else cntt++,id[cur]=cntt,t[cntt]=inf; 
	if(!son[cur]) return;
    dfs2(son[cur],cur,top,dis[cur]);
    for(int i=head[cur];i;i=e[i].nxt)
    {
        int j=e[i].to;
        if(!vis[j]) dfs2(j,cur,j,e[i].w); 
    }
    return;
}

int work(int x,int y)
{
    if(getf(x)!=getf(y)) return -1;
    int ans=inf;
    while(topf[x]!=topf[y])
    { 
		if(dep[topf[x]]<=dep[topf[y]]) swap(x,y);
		ans=min(ans,query(1,id[topf[x]],id[x]));
        x=fat[topf[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    if(x==y) return ans;
    else return ans=min(ans,query(1,id[son[x]],id[y]));
}

int main()
{
    read(n),read(m);
    for(int i=1;i<=m;i++) read(ed[i].x),read(ed[i].y),read(ed[i].z);
    sort(ed+1,ed+1+m,cmp),init(n);
    for(int i=1;i<=m;i++)
    {
        if(merge(ed[i].x,ed[i].y))
        {
            c++;
            add(ed[i].x,ed[i].y,ed[i].z),add(ed[i].y,ed[i].x,ed[i].z);
            if(c==n-1) break;
        }
    }
    for(int i=1;i<=n;i++) if(!vis[i]) fat[i]=i,dfs1(i,0,1);
    memset(vis,0,sizeof(vis)); 
	for(int i=1;i<=n;i++) if(!vis[i]) dfs2(i,i,i,0);
    build(1,1,cntt);
    read(q);
    while(q--)
    {
        read(x),read(y);
        printf("%d\n",work(x,y));
    }
    return 0;
}

\(9.\) P1038 神经网络

直接求一下深度,然后枚举每个深度的每一个点暴力更新即可。

怎么我写的这么长啊,怎么数据范围这么迷啊,怎么题解都用的拓扑排序啊。

时间复杂度 \(\mathcal O(n\log n+p)\)

#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
#include"algorithm"
using namespace std;

#define MAXN 105
#define read(x) scanf("%d",&x)

int n,m;
int c[MAXN],u[MAXN];
struct edge
{
	int to,nxt,w;
}e[MAXN*MAXN];
int head[MAXN],cnt=0;
int x,y,z;
struct node
{
	int id,dep;
}a[MAXN];
struct ans
{
	int id,val;
}b[MAXN];
int cntt=0;
int vis[MAXN],cc=0;
int beg[MAXN],mch[MAXN];
int maxd=0;
int flg=0;

bool cmp1(node n,node m){return n.dep<m.dep;}

bool cmp2(ans n,ans m){return n.id<m.id;}

void add(int u,int v,int c)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	e[cnt].w=c;
	head[u]=cnt;
}

void dfs(int cur,int de)
{
	a[++cc].id=cur,a[cc].dep=de,maxd=max(maxd,de);
	vis[cur]=1;
	for(int i=head[cur];i;i=e[i].nxt)
	{
		int j=e[i].to;
		if(!vis[j]) dfs(j,de+1);
	}
	return;
}


int main()
{
	read(n),read(m);
	for(int i=1;i<=n;i++) read(c[i]),read(u[i]);
	for(int i=1;i<=m;i++) read(x),read(y),read(z),add(x,y,z);
	for(int i=1;i<=n;i++) if(c[i]) dfs(i,1);
	sort(a+1,a+cc+1,cmp1);
	for(int i=1;i<=cc;i++){if(!beg[a[i].dep]) beg[a[i].dep]=i;mch[a[i].dep]++;}
	for(int i=1;i<=mch[1];i++) 
	{
		int k=a[i].id;
		if(c[k]<=0) continue;
		for(int s=head[k];s;s=e[s].nxt)
		{
			int j=e[s].to;
			c[j]+=e[s].w*c[k];
		}
	}
	for(int i=2;i<=maxd;i++)
	{
		for(int j=0;j<mch[i];j++)
		{
			int k=a[beg[i]+j].id;
			c[k]-=u[k];
			if(c[k]<=0) continue;
			for(int s=head[k];s;s=e[s].nxt)
			{
				c[e[s].to]+=e[s].w*c[k];
			}
		}
	}
	for(int i=beg[maxd];i<=cc;i++)
	{
		if(c[i]>0) b[++cntt].id=a[i].id,b[cntt].val=c[a[i].id];
	}
	if(!cntt){printf("NULL\n");return 0;}
	sort(b+1,b+cntt+1,cmp2);
	for(int i=1;i<=cntt;i++) printf("%d %d\n",b[i].id,b[i].val);
	return 0;
}

我这么困,竟然还能 1A???


\(10.\) P6477 [NOI Online #2 提高组]子序列问题(民间数据)

颜色不同性问题,考虑枚举每一个点的贡献,这也是多次枚举中减少枚举唯独的常用技巧。

可是考试的时候不会啊......

套路的维护一个前面最后一个出现的 \(pos\) 数组。

然后考虑把 \(f(i,i)\)\(f(i,n)\) 的贡献都加上。如果有新的贡献的话,再加上。

比如从前有 \(3\) 个颜色,现在加一个,那么最后加上的贡献为 \(16-9=7=2*3+1\)

于是我们维护序列代表从 \(i\) 发起的序列现在有几种颜色了。

我们需要增加贡献的是出现新颜色的区间,因为其他区间后面已默认没有出现新颜色加上贡献了,然后把出现新颜色的区间颜色数都加一。

不懂?举个栗子。

序列: \(1,2,3,1\)

我们只考虑从 \(1\) 号数发起的区间的贡献。

在第一个数时,有 \(0\) 个颜色,贡献 \(+1*4\)(有四个由这里发起的子序列)。

在第二个数时,有 \(1\) 个颜色,贡献 \(+3*3\)

在第三个数时,有 \(2\) 颜色,贡献 \(+5*2\)

在第四个数时,因为 前面更新时在最后一个数的贡献已经是 \(9\) 所以不用加了。

我们用线段树来维护所有的起点的子序列,复杂度是 \(\mathcal O(n\log n)\)

跑得慢啊

#include"iostream"
#include"cstdio"
#include"algorithm"
#include"cstring"
using namespace std;

#define MAXN 1000005 
#define MOD 1000000007
#define ll long long

int n,num[MAXN];
struct node
{
	int id,val;
}b[MAXN];
int lst=0,cnt=0;
int col[MAXN],pos[MAXN];
ll ans=0;

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 bool cmp(node n,node m){return n.val<m.val;}

inline void hsh(int n)
{
	sort(b+1,b+n+1,cmp);
	for(register int i=1;i<=n;i++)
	{
		if(b[i].val!=lst) lst=b[i].val,++cnt;
		num[b[i].id]=cnt;
	}
	return;
}

struct Tree
{
	int l,r;
	ll sum,lazy;
}a[MAXN<<2];

inline void update(int k){a[k].sum=a[k<<1].sum+a[k<<1|1].sum;}

inline void build(int k,int l,int r)
{
	a[k].l=l,a[k].r=r;
	if(l==r){a[k].sum=0;return;}
	int mid=(l+r)>>1;
	build(k<<1,l,mid),build(k<<1|1,mid+1,r);
	update(k);return;
}

inline void lazydown(int k)
{
	if(a[k].l==a[k].r){a[k].lazy=0;return;}
	a[k<<1].sum+=(a[k<<1].r-a[k<<1].l+1)*a[k].lazy;
	a[k<<1|1].sum+=(a[k<<1|1].r-a[k<<1|1].l+1)*a[k].lazy;
	a[k<<1].lazy+=a[k].lazy;
	a[k<<1|1].lazy+=a[k].lazy;
	a[k].lazy=0;
	return;
}

inline void turn(int k,int l,int r)
{
	if(a[k].lazy) lazydown(k);
	if(a[k].l==l&&a[k].r==r)
	{
		a[k].sum+=(ll)(a[k].r-a[k].l+1);
		a[k].lazy++;
		return;
	}
	int mid=(a[k].l+a[k].r)>>1;
	if(r<=mid) turn(k<<1,l,r);
	else if(l>mid) turn(k<<1|1,l,r);
	else turn(k<<1,l,mid),turn(k<<1|1,mid+1,r);
	update(k);
}

inline ll query(int k,int l,int r)
{
	if(a[k].lazy) lazydown(k);
	if(a[k].l==l&&r==a[k].r) return a[k].sum;
	int mid=(a[k].l+a[k].r)>>1;
	if(r<=mid) return query(k<<1,l,r);
	else if(l>mid) return query(k<<1|1,l,r);
	else return query(k<<1,l,mid)+query(k<<1|1,mid+1,r);
}

int main()
{
	n=read();
	for(register int i=1;i<=n;i++) b[i].val=read(),b[i].id=i;	
	hsh(n);
	for(register int i=1;i<=n;i++) pos[i]=col[num[i]],col[num[i]]=i;
	build(1,1,n);
	for(register int i=1;i<=n;i++)
	{
		ll now=query(1,pos[i]+1,i);
		now=now*2+i-pos[i];
		ans=(ans%MOD+(n-i+1)%MOD*now%MOD)%MOD;
		turn(1,pos[i]+1,i);
	}
	printf("%lld\n",ans%MOD);
	return 0;
}//开O2才能过吧

\(11.\) P1031 均分纸牌

挺妙的,其实就是从左到右扫一遍,如果从上一个起点到这里正好是可以均分的,那么下一个是一个新起点,这里的最后一个与下一块不沟通,所以操作次数从 \(n\) 中减一。

时间复杂度是 \(\mathcal O(n)\)

#include"iostream"
#include"cstdio"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 105

int n,a[MAXN];
int sum=0,av=0;
int ans;

int main()
{
	read(n);
	ans=n;
	for(int i=1;i<=n;i++) read(a[i]),av+=a[i];
	av/=n;
	for(int i=1;i<=n;i++)
	{
		sum+=a[i];
		if(sum==av*i) ans--;
	}
	printf("%d\n",ans);
	return 0;
}

\(12.\) P4905 报纸

暴力暴力暴力

把往下和往右作为主导,然后暴力判断每一个格的 \(\gcd\),然后贪心的选就好了。

复杂度是 \(\mathcal O(n^2\log n)\),并没有蓝题难度。


\(13.\) P1896 [SCOI2005]互不侵犯

\(01\) 串代表是否被选,然后判断每一个状态是否可行。

可以开三维数组代表状态,共选几个位置,行数。

这里用一种方式压掉了行数这一维(难道这就是滚动数组?算了算了,状压 dp 我都没正经学过还 A 了这题呢),然而大大增加了码量。

然后空间复杂度应该是 \(\mathcal O(2^nk)\),时间复杂度应该是 \(\mathcal O(2^{2n}n(n+k))\)

#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN (1<<9)+5
#define ll long long

int n,k;
ll a[MAXN][85],b[MAXN][85];
bool flag[10],mark=false;
int cnt=0;
ll ans=0;

int main()
{
	read(n),read(k);
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	for(int s=0;s<(1<<n);s++)
	{
		mark=false,cnt=0;
		for(int i=0;i<n;i++){if((1<<i)&s) flag[i]=1;else flag[i]=0;}
		{
			if(flag[i]&&flag[i-1]) mark=true;
			if(flag[i]) cnt++;
		}
		if(flag[0]) cnt++;
		if(mark) a[s][cnt]=0;
		else a[s][cnt]=1ll;
	}
	for(int qwq=2;qwq<=n;qwq++)
	{
		if(qwq%2==0)
		{
			memset(b,0,sizeof(b));
			for(int s1=0;s1<(1<<n);s1++)
			{
				for(int s2=0;s2<(1<<n);s2++)
				{
					mark=false,cnt=0;
					for(int i=0;i<n;i++)
					{
						if(s2&(1<<i))
						{
							if(i>=1&&(s1&(1<<(i-1)))) mark=true;
							if(s1&(1<<i)) mark=true;
							if(i<n&&(s1&(1<<(i+1)))) mark=true;
						}
						if(mark) break;
					}
					for(int i=1;i<n;i++) if(((1<<i)&s2)&&((1<<(i-1))&s2)) mark=true;
					if(mark) continue;
					for(int i=0;i<n;i++) if(s2&(1<<i)) cnt++;
					for(int i=cnt;i<=k;i++) b[s2][i]+=a[s1][i-cnt];
				}
			}

		}
		else 
		{
			memset(a,0,sizeof(a));
			for(int s1=0;s1<(1<<n);s1++)
			{
				for(int s2=0;s2<(1<<n);s2++)
				{
					mark=false,cnt=0;
					for(int i=0;i<n;i++)
					{
						if(s2&(1<<i))
						{
							if(i>=1&&(s1&(1<<(i-1)))) mark=true;
							if(s1&(1<<i)) mark=true;
							if(i<n&&(s1&(1<<(i+1)))) mark=true;
						}
						if(mark) break;
					}
					for(int i=1;i<n;i++) if(((1<<i)&s2)&&((1<<(i-1))&s2)) mark=true;
					if(mark) continue;
					for(int i=0;i<n;i++) if(s2&(1<<i)) cnt++;
					for(int i=cnt;i<=k;i++) a[s2][i]+=b[s1][i-cnt];
				}
			}
		}
	}
	if(n%2==0) for(int s=0;s<(1<<n);s++) ans+=b[s][k];
	else for(int s=0;s<(1<<n);s++) ans+=a[s][k];
	printf("%lld\n",ans);
}

\(14.\) P4071 [SDOI2016]排列计数

错排问题。

递推式 :

\[D_n=(n-1)(D_{n-1}+D_{n-2}) \]

冷静分析之后发现答案是:

\[\dbinom{n}{m} D_{n-m} \]

复杂度是 \(\mathcal O(n+T\log p)\)

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 1000005
#define MOD 1000000007

int t,n,m;
int d[MAXN],jc[MAXN];

void get()
{
	d[0]=1,jc[0]=1;
	d[1]=0,d[2]=1;
	jc[1]=1,jc[2]=2;
	for(int i=3;i<=1000000;i++)
	{
		d[i]=1ll*(i-1)*(d[i-1]+d[i-2])%MOD;
		jc[i]=1ll*jc[i-1]*i%MOD;
	}
	return;
}

int quickpow(int n,int k)
{
	int ans=1,base=n;
	while(k)
	{
		if(k&1) ans=1ll*ans*base%MOD;
		k>>=1;
		base=1ll*base*base%MOD;
	}
	return ans%MOD;
}

int inv(int a){return quickpow(a,MOD-2);}

int com(int n,int m){return 1ll*jc[n]*inv(1ll*jc[m]*jc[n-m]%MOD)%MOD;}

int main()
{
	get();
	read(t);
	while(t--)
	{
		read(n),read(m);
		printf("%d\n",(int)(1ll*com(n,m)*d[n-m]%MOD));
	}
	return 0;
} 

\(15.\) P4513 小白逛公园

这不是线段树最大子段和板子题吗?

只是加了个单点修改,反正不是区间修改,就能搞。

时间复杂度 \(\mathcal O(m\log n)\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 500005

int n,m;
int t[MAXN],type,l,r;
struct Tree
{
	int l,r,pre,suf;
	int sum,maxn;
	Tree(){l=r=pre=suf=sum=maxn=0;}
}a[MAXN<<2];

inline void update(int k)
{
	a[k].sum=a[k<<1].sum+a[k<<1|1].sum;
	a[k].pre=max(a[k<<1].pre,a[k<<1].sum+a[k<<1|1].pre);
	a[k].suf=max(a[k<<1|1].suf,a[k<<1|1].sum+a[k<<1].suf);
	a[k].maxn=max(a[k<<1].suf+a[k<<1|1].pre,max(a[k].pre,max(a[k<<1].maxn,max(a[k<<1|1].maxn,a[k].suf))));	
}

void build(int k,int l,int r)
{
	a[k].l=l,a[k].r=r;
	if(l==r){a[k].sum=a[k].pre=a[k].suf=a[k].maxn=t[l];return;}
	int mid=(l+r)>>1;
	build(k<<1,l,mid),build(k<<1|1,mid+1,r);
	update(k);
}

void turn(int k,int x,int y)
{
	if(a[k].l==a[k].r){a[k].sum=a[k].pre=a[k].suf=a[k].maxn=y;return;}
	int mid=(a[k].l+a[k].r)>>1;
	if(x<=mid) turn(k<<1,x,y);
	else turn(k<<1|1,x,y);
	update(k);
}

Tree query(int k,int l,int r)
{
	if(a[k].l==l&&a[k].r==r) return a[k];
	int mid=(a[k].l+a[k].r)>>1;
	if(r<=mid) return query(k<<1,l,r);
	else if(l>mid) return query(k<<1|1,l,r);
	else
	{
		Tree ans,g=query(k<<1,l,mid),h=query(k<<1|1,mid+1,r);
		ans.pre=max(g.pre,g.sum+h.pre);
		ans.suf=max(h.suf,h.sum+g.suf);
		ans.sum=g.sum+h.sum;
		ans.maxn=max(g.maxn,max(h.maxn,max(ans.pre,max(ans.suf,g.suf+h.pre))));
		return ans;
	}
}

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++)
	{
		read(type),read(l),read(r);
		if(type==1&&l>r) swap(l,r);
		if(type==1) printf("%d\n",query(1,l,r).maxn);
		else turn(1,l,r);
	}
	return 0;
}

\(16.\) P1483 序列变换

这个操作二这么少就不正常。

考虑每一次操作二什么时候被影响,其实是当被操作一中 \(x\) 整除的时候。

那就对每一个 \(x\) 打标记,然后操作二中看他有多少因数。

设有 \(m_1\) 个操作一, \(m_2\) 个操作二,那么复杂度是 \(\mathcal O(m_1+m_2\sqrt{x})\)

#include"iostream"
#include"cstdio"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 1000005

int n,m;
int a[MAXN],c[MAXN];
int t,x,y;

inline int dfs(int n)
{
	int ans=0;
	for(int i=1;i*i<=n;i++)
	{
		if(n%i==0)
		{
			if(i*i==n) ans+=c[i];
			else ans=ans+c[i]+c[n/i]; 
		}
	}
	return ans;
}

int main()
{
	read(n),read(m);
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<=m;i++)
	{
		read(t),read(x);
		if(t==1) read(y),c[x]+=y;
		else printf("%d\n",a[x]+dfs(x));	
	} 
	return 0;
}

\(17.\) P1533 可怜的狗狗

主席树板子。

能卡 \(21\) 倍空间,\(21\) 倍空间 \(WA\),更少 \(RE\)

代码不好看,不贴了。

时间复杂度 \(\mathcal O((n+m)\log n)\)


\(18.\) P2704 [NOI2001]炮兵阵地

又是一道状压 \(dp\)

考虑用 \(dp[s1][s2][k]\) 代表枚举到第 \(k\) 行得到的上一行状态为 \(s_1\),上上行状态为 \(s_2\) 的最大方案数。

然后暴力判是否合法即可。

这样空间复杂度是 \(\mathcal O(2^{2n}m)\),时间复杂度是 \(\mathcal O(2^{2n}m^2)\)

考虑像 互不侵犯 那题一样写一个 假的 滚动数组,空间复杂度就是 \(\mathcal O(2^{2n})\) 了。

时间稍微卡一些能过(确信)。

看了看题解,发现题解的方法真妙啊。

我们可以对每一行先排除掉一些不合法的答案,然后存起来,直接枚举这些合法的就好了。


\(19.\) P2184 贪婪大陆

不会吧不会吧,就我一个人写的平衡树吗?<-太菜了,看不出树状数组

我们考虑每个询问前面的区间对此询问的贡献。

显然,只有两个区间有交集,才能对答案造成贡献。

然而交集不好维护,思考从反面解决问题,即哪些区间对答案没有贡献。

显然修改区间 \([l,r]\) 对询问区间 \([L,R]\) 没有贡献,当且仅当 \(l>R\)\(r<L\)。由于 \(l\leq r\),则两种情况不可能同时满足。

其实有点像偏序问题,但我们不向那方面想试试。

那么我们维护两颗平衡树,分别代表修改的左右区间的集合,然后每次询问询问区间的左右端点的排名即可。

这里是用替罪羊树实现的,复杂度是 \(\mathcal O(m\log m)\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 100005
#define alpha 0.7666

int n,m;
int t,l,r;
int sum=0;
struct Tree
{
	int root,cnt,c;
	int ls[MAXN],rs[MAXN];
	int wn[MAXN],sh[MAXN];
	int val[MAXN];
	int rt[MAXN];
	
	bool judge(int k){return wn[k]&&(double)sh[k]*alpha<(double)max(sh[ls[k]],sh[rs[k]]);}
	
	void update(int k){sh[k]=sh[ls[k]]+sh[rs[k]]+wn[k];}
	
	void unfold(int k)
	{
		if(!k) return;
		unfold(ls[k]);
		if(wn[k]) rt[++c]=k;
		unfold(rs[k]);
	}
	
	int build(int l,int r)
	{
		if(l>=r) return 0;
		int mid=(l+r)>>1;
		ls[rt[mid]]=build(l,mid);
		rs[rt[mid]]=build(mid+1,r);
		update(rt[mid]);
		return rt[mid];
	}
	
	void bal(int &k){c=0;unfold(k);k=build(1,c+1);}
	
	void insert(int &k,int v)
	{
		if(!k)
		{
			k=++cnt;
			if(!root) root=1;
			wn[k]=sh[k]=1,val[k]=v;
			ls[k]=rs[k]=0;
			return;
		}
		else
		{
			if(val[k]==v) wn[k]++;
			else if(val[k]>v) insert(ls[k],v);
			else insert(rs[k],v);
			update(k);
			if(judge(k)) bal(k);
		}
		return;
	}
	
	int rkup(int k,int v)
	{
		if(!k) return 0;
		if(val[k]==v) return sh[ls[k]];
		else if(val[k]>v) return rkup(ls[k],v);
		else return wn[k]+sh[ls[k]]+rkup(rs[k],v);
	}
	
	int rkdown(int k,int v)
	{
		if(!k) return 0;
		if(val[k]==v) return sh[ls[k]]+wn[k];
		else if(val[k]>v) return rkdown(ls[k],v);
		else return wn[k]+sh[ls[k]]+rkdown(rs[k],v);
	}
}Tl,Tr;

int main()
{
	read(n),read(m);
	for(int i=1;i<=m;i++)
	{
		read(t),read(l),read(r);
		if(t==1)
		{
			Tl.insert(Tl.root,r);
			Tr.insert(Tr.root,l);
			sum++;
		}
		else
		{
			int ans=sum;
			int now=Tl.rkup(Tl.root,l);
			ans-=now;
			now=Tr.rkdown(Tr.root,r);
			now=sum-now;
			ans-=now;
			printf("%d\n",ans);
		}
	}
	return 0;
}


\(20.\) P1342 请柬

两个方向的的图其它点到 \(1\) 号点的最短路和。

剩下的就是板子了,练了一下 \(dij\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
#include"cstring"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 1000005 
#define inf 1ll<<60
#define ll long long
#define mem(s) memset(s,0,sizeof(s))

int n,m;
int u[MAXN],v[MAXN],w[MAXN];
struct edge
{
	int to,nxt,w;
		
}e[MAXN];
int head[MAXN],cnt=0;
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
ll dis[MAXN];
int vis[MAXN];

void add(int u,int v,int w)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	e[cnt].w=w;
	head[u]=cnt;
}

ll dij()
{
	while(!q.empty())
	{
		int s=q.top().second;q.pop();
		if(vis[s]) continue;
		else vis[s]=1;
		for(int i=head[s];i;i=e[i].nxt)
		{
			int j=e[i].to;
			if(dis[j]>dis[s]+(ll)e[i].w) dis[j]=dis[s]+(ll)e[i].w,q.push(make_pair(dis[j],j));
		}
	}
	ll ans=0;
	for(int i=2;i<=n;i++) ans+=dis[i];
	return ans;
}

int main()
{
	read(n),read(m);
	for(int i=1;i<=m;i++) read(u[i]),read(v[i]),read(w[i]);
	for(int i=1;i<=m;i++) add(u[i],v[i],w[i]);
	for(int i=2;i<=n;i++) dis[i]=inf;
	q.push(make_pair(0,1));
	ll sum=dij();
	mem(e),mem(head),mem(dis),mem(vis),cnt=0;
	while(!q.empty()) q.pop();
	for(int i=1;i<=m;i++) add(v[i],u[i],w[i]);
	for(int i=2;i<=n;i++) dis[i]=inf;
	q.push(make_pair(0,1));
	sum+=dij();
	printf("%lld",sum);
	return 0;
} 

\(21.\) P3833 [SHOI2012]魔法树

树剖板子题啊,,,,,,

就是码量大了一些,没必要写了。


\(22.\) P1558 色板游戏

开始想不出来,然后发现 \(T\) 只有 \(30\),那我们就对每个颜色建一棵线段树,然后每次补一个区间清零,最后看看这个区间有哪些颜色权值大于 \(0\) 就好了。


\(23.\) P4588 [TJOI2018]数学计算

维护一颗线段树,每个位置为现在这个时间下没有乘这个位置的数的答案。

然后区间乘查询就好了。

多测记得清空,时间复杂度 \(\mathcal O(Tq\log q)\)


\(24.\) P4880 抓住czx

最短路板子题,枚举一下可能出现的位置然后判断打擂台即可。

#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
#include"queue"
#include"vector"
#include"algorithm"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 500005
#define inf 2147483647

int n,m,t,b,ee;
int dis[100005],vis[100005];
struct node
{
	int to,nxt,w;
}e[MAXN<<1];
int head[100005],cnt=0;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
int l,r,c;
struct Pos
{
	int ti,loc;
}pos[100005]; 
int minx=inf,maxt=0,fin;

bool cmp(Pos n,Pos m){return n.ti<m.ti;}

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 g=q.top().second;
		q.pop();
		if(vis[g]) continue;
		vis[g]=1;
		for(int i=head[g];i;i=e[i].nxt)
		{
			int j=e[i].to;
			if(dis[j]>dis[g]+e[i].w)
			{
				dis[j]=dis[g]+e[i].w;
				q.push(make_pair(dis[j],j));
			}
		}
	}
	return;
}

int main()
{
	read(n),read(m),read(b),read(ee);
	pos[1].ti=0,pos[1].loc=ee;
	for(int i=1;i<=m;i++) read(l),read(r),read(c),add(l,r,c),add(r,l,c);
	read(t);
	for(int i=2;i<=t+1;i++) read(pos[i].ti),read(pos[i].loc);
	for(int i=1;i<=n;i++) dis[i]=inf;
	dis[b]=0,q.push(make_pair(0,b));
	dij();
	sort(pos+1,pos+t+2,cmp);
	pos[t+2].ti=inf;
	for(int i=1;i<=t+1;i++)
	{
		if(dis[pos[i].loc]<pos[i+1].ti) minx=min(minx,max(dis[pos[i].loc],pos[i].ti));
	}
	if(minx==inf) minx=dis[pos[t+1].loc];
	printf("%d\n",minx);
	return 0;
}

\(25.\) P1126 机器人搬重物

颓 废 到 写 爆 搜。

其实是感觉自己不太会写爆搜了,所以练一下

挺难写的,但没有任何思维难度。

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define check(a,b,c,d,x,y) if(dp[a][b][c]>dp[x][y][d]+1) dp[a][b][c]=dp[x][y][d]+1,dfs(a,b,c);//define简化大法好

int n,m;
int x,y,z,w,dir;
int f[55][55];
char c;
int dp[55][55][5];

inline void dfs(int x,int y,int dir)
{
	if(x==z&&y==w) return;
	if(dir==1)
	{
		check(x,y,2,1,x,y);
		check(x,y,4,1,x,y);
		if(!f[x][y+1]&&y<m-1)
		{
			check(x,y+1,1,1,x,y);
			if(!f[x][y+2]&&y<m-2)
			{
				check(x,y+2,1,1,x,y);
				if(!f[x][y+3]&&y<m-3) check(x,y+3,1,1,x,y);
			}
		}
	}
	else if(dir==2)
	{
		check(x,y,3,2,x,y);check(x,y,1,2,x,y);
		if(!f[x+1][y]&&x<n-1)
		{
			check(x+1,y,2,2,x,y);
			if(!f[x+2][y]&&x<n-2)
			{
				check(x+2,y,2,2,x,y);
				if(!f[x+3][y]&&x<n-3) check(x+3,y,2,2,x,y);
			}
		}
	}
	else if(dir==3)
	{
		check(x,y,4,3,x,y);check(x,y,2,3,x,y);
		if(y>1)
		{
			if(!f[x][y-1])
			{
				check(x,y-1,3,3,x,y);
				if(y>2)
				{
					if(!f[x][y-2])
					{
						check(x,y-2,3,3,x,y);
						if(y>3) if(!f[x][y-3]) check(x,y-3,3,3,x,y);
					}
				}
			}
		}
	}
	else
	{
		check(x,y,1,4,x,y);check(x,y,3,4,x,y);
		if(x>1)
		{
			if(!f[x-1][y])
			{
				check(x-1,y,4,4,x,y);
				if(x>2)
				{
					if(!f[x-2][y])
					{
						check(x-2,y,4,4,x,y);
						if(x>3) if(!f[x-3][y]) check(x-3,y,4,4,x,y);
					}
				}
			}
		}
	}
	return;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			int l;
			scanf("%d",&l);
			if(l==1)
			{
				f[i-1][j]=1;
				f[i-1][j-1]=1;
				f[i][j]=1;
				f[i][j-1]=1;
			}
		}
	}
	scanf("%d%d%d%d",&x,&y,&z,&w);cin>>c;
	if(c=='E') dir=1;
	else if(c=='S') dir=2;
	else if(c=='W') dir=3;
	else dir=4;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) for(int k=1;k<=4;k++) dp[i][j][k]=2147483647;
	dp[x][y][dir]=0;
	dfs(x,y,dir);
	if(min(dp[z][w][1],min(dp[z][w][2],min(dp[z][w][3],dp[z][w][4])))>100000000) printf("%d\n",-1);
	else printf("%d\n",min(dp[z][w][1],min(dp[z][w][2],min(dp[z][w][3],dp[z][w][4]))));
	return 0;
} 

\(26.\) P1108 低价购买

乍一看:啊,这不是最长不上升子序列板子吗?

不是,要输出方案数。

首先考虑从所有方案中减去相同的方案,但是不太会啊......

然后想到在 \(dp\) 过程中筛掉,所以考虑不筛情况下,我们要枚举每个值和他前面的所有值。

那么既然方案一样,那我们我们找一下和现在这个数一样的前面的数,假使他的最长子序列长度与更新后的现在的这个一样,那就意味着出现重复,果断把前面那些扔掉。

这个过程用栈来实现,时间复杂度是 \(\mathcal O(n^2)\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"stack"
using namespace std;

int n,a[5005];
int dp[5005][5005]={0},maxx[5005];
int maxn=1;
long long sum=0;
stack<int> s;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),dp[i][1]=1,maxx[i]=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<i;j++)
		{
			if(a[i]>a[j]) continue;
			else if(a[i]==a[j]){s.push(j);continue;}
			dp[i][maxx[j]+1]+=dp[j][maxx[j]];
			maxx[i]=max(maxx[i],maxx[j]+1);
			maxn=max(maxn,maxx[i]);
		}
		while(!s.empty())
		{
			int t=s.top();
			s.pop();
			dp[t][maxx[t]]=0;
		}
	}
	for(int i=maxn;i<=n;i++) sum+=(long long)dp[i][maxn];
	printf("%d %lld\n",maxn,sum);
	return 0;
}

\(27.\) P4826 [USACO15FEB]Superbull S

\(\mathcal O(n^2)\) 预处理是个人就能想到,不过我有点 sb,并没有想到最大生成树,枯了。


\(28.\) P2456 [SDOI2006]二进制方程

一道不错的并查集题目。

开始看一头雾水,不过当你对整个式子展开时,就豁然开朗了。

比如说样例 \(2\)

展开后等式两边就成了这个:

\(1,b_1,b_2,a_1,a_2,a_3,a_4,d_1,d_2,d_3,d_4,1\)

和:

\(a_1,a_2,a_3,a_4,c_1,c_2,c_3,c_4,b_1.b_2,e_1,e_1\)

然后你就可以轻松的找到每一位上的对应关系,比如:

\[a_2=b_1=c_1=d_2 \]

然后你给每个字母的第 \(i\) 位编个号,然后用并查集来维护即可。

这里要注意是否有同一组字母同时对应着 \(0\)\(1\)

最后补上高精即可。

#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
#include"cstring"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 10005
#define ll unsigned long long

int k;
int f[MAXN],vis[MAXN],l[30],be[30];
int a[MAXN],b[MAXN];
char s[MAXN];
int cnt=0,sum=0;
int v[30],c=0;
int ans[100005],li=1;

void init(int n){for(int i=1;i<=n;i++) vis[i]=0,f[i]=i;}

int getf(int u){return f[u]=(f[u]==u)?u:getf(f[u]);}

void merge(int u,int v){int t1=getf(u),t2=getf(v);if(t1!=t2) f[t2]=t1;}

int tran(char c){return c-'a'+1;}

int main()
{
    read(k),be[1]=0;
    for(int i=1;i<=k;i++) read(l[i]),be[i+1]+=be[i]+l[i];
    scanf("%s",s);
    int lens=strlen(s);
    for(int i=0;i<lens;i++)
    {
        if(s[i]=='0') a[++cnt]=-1;
        else if(s[i]=='1') a[++cnt]=0;
        else
        {
            int op=tran(s[i]);
            for(int j=1;j<=l[op];j++) a[++cnt]=be[op]+j;
        }
    }
    int to=cnt;
    cnt=0;
    scanf("%s",s);
    lens=strlen(s);
    for(int i=0;i<lens;i++)
    {
        if(s[i]=='0') b[++cnt]=-1;
        else if(s[i]=='1') b[++cnt]=0;
        else
        {
            int op=tran(s[i]);
            for(int j=1;j<=l[op];j++) b[++cnt]=be[op]+j;
        }
    }
    if(cnt!=to) return puts("0"),0;
    init(be[k]+l[k]);
    for(int i=1;i<=cnt;i++)
    {
        if((a[i]==0&&b[i]==-1)||(a[i]==-1&&b[i]==0)) return puts("0"),0;
        else if((a[i]>=-1&&a[i]<=0)||(b[i]>=-1&&b[i]<=0)) continue;
        merge(a[i],b[i]);
    }
    for(int i=1;i<=cnt;i++)
    {
    	if(a[i]>=-1&&a[i]<=0&&b[i]>=-1&&b[i]<=0) continue;
        if(a[i]==0)
        {
            int now=getf(b[i]);
            if(vis[now]==1) return puts("0"),0;
            vis[now]=2;
        }
        else if(a[i]==-1)
        {
        	int now=getf(b[i]);
            if(vis[now]==2) return puts("0"),0;
            vis[now]=1;
         }
        else if(b[i]==-1)
        {
            int now=getf(a[i]);
            if(vis[now]==2) return puts("0"),0;
            vis[now]=1;
        }
        else if(b[i]==0)
        {
            int now=getf(a[i]);
            if(vis[now]==1) return puts("0"),0;
            vis[now]=2;
        }
    }
    for(int i=1;i<=be[k]+l[k];i++) if(f[i]==i&&!vis[i]) sum++;
	ans[1]=1;
	for(int i=1;i<=sum;i++)
    {
    	for(int j=1;j<=li;j++)
    	{
    		ans[j]*=2;
		}
		for(int j=1;j<=li;j++)
		{
			if(ans[j]>=10) ans[j+1]+=ans[j]/10,ans[j]%=10;
		}
		if(ans[li+1]>0) li++;
		while(ans[li]>=10) ans[li+1]+=ans[li]/10,ans[li]%=10,li++;
	}
	for(int i=li;i>=1;i--) printf("%d",ans[i]);
    cout<<endl;
    return 0;
}

\(29.\) P4116 Qtree3

树剖板子题,如果改成黑色就加一个参加打擂台的点,否则踢掉一个。


\(30.\) P2797 Facer的魔法

题解:https://www.luogu.com.cn/blog/hanzhongtlx-juruo/solution-cf626e

复杂度 \(\mathcal O(n\log n)\)


\(31.\) CF626E Simple Skewness

和上面那个双倍经验关系。


\(32.\) CF997A Convert to Ones

挺好的题。

考虑到翻转的最大效益是让一个连续的 1 串到一边去,直接贡献就是少平推一次。

最后无论如何都需要平推一次。

于是记录本需要平推的 0 串的个数 \(sum\),于是贪心选取最小的步骤搞 \(sum-1\) 次,最后平推一次就好了。

时间复杂度是 \(\mathcal O(len)\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;

#define ll long long
#define read(x) scanf("%d",&x)

int n;
int a,b;
char c[300005];
int lst=1,sum=0;

int main()
{
	read(n),read(a),read(b);
	scanf("%s",c);
	int len=strlen(c);
	for(int i=0;i<len;i++)
	{
		if(lst==1&&c[i]=='0') sum++;
		lst=(int)c[i]-'0';
	}
	if(sum==0) printf("0\n");
	else printf("%lld\n",1ll*b+1ll*(sum-1)*((ll)min(a,b)));
	return 0;
}

\(33.\) P1463 [POI2002][HAOI2007]反素数

草草草,怎么是打表题???

首先我们知道因数个数公式:

\[\sigma(a)=\prod (k_i+1) \]

显然 \(k_i\) 不受其底数 \(p_i\) 的约束,故尽量用小的质数。

我们手玩发现 \(\prod p_i\leq 2e9\) 的最大质数为 \(29\) ,仅有 \(10\) 个,考虑爆搜。

然而会超时。

我们选取单调性。

即在爆搜时只能选取比上一个选取大或者相等的质数,这样去掉了很多重复的枚举,也能得到答案。

而且此时我们得到有最大因数的最小数字即为答案。

时间复杂度为 \(\mathcal O(\text{能过})\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;

#define ll long long

int n;
int pri[15]={0,2,3,5,7,11,13,17,19,23,29};
int maxn=0,num;
int cnt[15]={0};
int kl=0;

void dfs(int lst,int now)
{
	if(1ll*now*2>1ll*n)
	{
		int nw=1;
		for(int i=1;i<=10;i++) nw*=(1+cnt[i]);
		if(nw>maxn||(nw==maxn&&now<num)) num=now,maxn=nw;
	}
	for(int i=max(lst,1);i<=10;i++)
	{
		if(1ll*now*pri[i]<=(ll)n) cnt[i]++,dfs(i,now*pri[i]);		
	}
	cnt[lst]--;
	return;
}

int main()
{
	scanf("%d",&n);
	dfs(0,1);
	printf("%d\n",num);
	return 0;
}

\(34.\) P2650 弹幕考察

不需要任何数据结构维护。

如果是随插入随询问,可以参考 P2184 ,确实需要树状数组/线段树,甚至我用了平衡树。

但是这个是先全部插入再询问。

于是我们将左右区间分开看。

显然只有观察区间左端点比弹幕区间的右端点还靠右才不会产生贡献,右端点同理。

嗯,我可以想到二分答案来记录,不需要树状数组,也不需要离散化。

时间复杂度 \(\mathcal O(n\log n)\)


\(35.\) P3539 [POI2012]ROZ-Fibonacci Representation

如果 \(k\leq 1e6\) 容易想到 dp,但是这个数据范围很不友好。

考虑到斐波那契数列中一项是由其他多项加和得到的,所以优先选取大一些的一定不亏。

于是找离本数最近的数,做差然后去绝对值(因为这个过程可以加减,正负不影响结果,全弄成正数),不断毕竟 \(0\) 即可得到答案。

我们用二分实现找离得最近的数的过程,最多迭代 \(\mathcal O(\log n)\) 次,故最终复杂度为 \(\mathcal O(p\log k\log \log k)\)(斐波那契数列大概是 \(\mathcal O(\log n)\) 的)。

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define read(x) scanf("%d",&x)
#define ll long long 

ll f[100]; 
int p;
ll n;

int main()
{
	f[1]=f[2]=1ll;
	for(int i=3;i<=90;i++) f[i]=f[i-1]+f[i-2];
	read(p);
	while(p--)
	{
		scanf("%lld",&n);
		ll ans=0;
		while(n>0)
		{
			int l=2,r=90,mid;
			while(l<r)
			{
				mid=(l+r)>>1;
				if(f[mid]<=n) l=mid+1;
				else r=mid;
			}
			ll now1=abs(n-f[l]),now2=abs(n-f[l-1]);
			if(now1<now2) n=now1;
			else n=now2;
			ans++;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

\(36.\) P4977 毒瘤之神异之旅

只会 \(\mathcal O(n^3)\) dp 还是太菜了 。

划分数 \(f_{i,j}=f_{i-1,j-1}+f_{i-j,j}\)

分别代表第一个数是不是 \(1\),时间复杂度是 \(\mathcal O(n^2)\)

好像要卡空间,但是可以卡过去??????

反正不愿写了/kk。


\(37&38\).

这场比赛的前 \(2/3\) 题挺一眼,所以切了。

比赛链接:contest

\(B\) 题挺不错的说。


\(39.\) P2988 [USACO10MAR]Test Taking S

开始想二分 \(n\) 然后才知道这玩意没什么单调性。

于是枚举每一个 \(n\) ,二分最近的 \(t\) 即可。

时间复杂度是 \(\mathcal O((n+k)\log k)\)

发现最近的 \(t\) 是单调的,故维护指针可以做到 \(\mathcal O(k\log k+n+k)\)


\(40.\) P1769 淘汰赛制

比较简单的一道概率 \(dp\) ,但是 sb 了辣么久/kk。

考虑设 \(dp_{i,j}\) 为比到第 \(i\) 轮某个人胜利的概率。

显然有 \(dp_{i,j}=\sum\limits_{s=l}^r dp_{i-1,j}×dp_{i-1,s}×p_{j,s}\)

这里 \(l,r\) 是啥应该比较好想但比较难用式子表示,所以不具体写了,但我竟然傻了。

初始值 \(dp_{0,i}=1\)

记得化一下百分数,不要用 long long 而且用 double 不用怕被卡精度。

时间复杂度 \(\mathcal O(2^{2l})\) 很好证。

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define read(x) scanf("%d",&x)
#define ll long long

int l;
double p[1050][1050];
double dp[12][1050];
double maxn=0;
int num;

int main()
{
	read(l);
	int k=1<<l;
	for(int i=1;i<=k;i++) for(int j=1;j<=k;j++) scanf("%lf",&p[i][j]),p[i][j]=p[i][j]/100;
	for(int i=1;i<=k;i++) dp[0][i]=1.0;
	for(int i=1;i<=l;i++)
	{
		int rt=1<<i;
		for(int j=1;j<=k;j++)
		{
			int loc=floor((double)(j-1)/rt);
			double mid=(double)(1+rt)/2+loc*rt;
			int be,st;
			if((double)j<mid) be=ceil(mid),st=loc*rt+rt;
			else be=loc*rt+1,st=floor(mid);
			for(int s=be;s<=st;s++) dp[i][j]=dp[i][j]+dp[i-1][j]*p[j][s]*dp[i-1][s];
		}	
	}	
	for(int i=1;i<=k;i++) if(dp[l][i]>maxn) maxn=dp[l][i],num=i;
	printf("%d\n",num);
	return 0;
} 

\(41.\) P3003 [USACO10DEC]Apple Delivery S

最短路板子题。

\[ans=\min(dis_{p_b,p_{a1}},dis_{p_b,p_{a2}})+dis_{p_{a1},p_{a2}} \]

时间复杂度为 \(\mathcal O(n\log n)\)


\(42.\) P4025 [PA2014]Bohater

一眼贪心。

第二眼发现是这个题(P3619) 的翻版。

其实就是需要 \(d_i\),打败后的获得为 \(a_i-d_i\)

然后套路一波,复杂度是 \(\mathcal O(n\log n)\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 100005
#define ll long long

int n,x;
int c,d;
struct node1
{
	int ne,ad,id;
}a[MAXN];
struct node2
{
	int ne,ad,id;
}b[MAXN];
int cnt=0,num=0;

bool cmp1(node1 n,node1 m){return n.ne<m.ne;}

bool cmp2(node2 n,node2 m){return n.ne+n.ad>m.ne+m.ad;}

int main()
{
	read(n),read(x);
	ll xx=(ll)x;
	for(int i=1;i<=n;i++)
	{
		read(c),read(d);
		int op=d-c;
		if(op>0) a[++cnt].ne=c,a[cnt].ad=op,a[cnt].id=i;
		else b[++num].ne=c,b[num].ad=op,b[num].id=i;
	}
	sort(a+1,a+cnt+1,cmp1),sort(b+1,b+1+num,cmp2);
	for(int i=1;i<=cnt;i++)
	{
		if(xx<=(ll)a[i].ne) return puts("NIE"),0;
		else xx+=(ll)a[i].ad;
	}
	for(int i=1;i<=num;i++)
	{
		if(xx<=(ll)b[i].ne) return puts("NIE"),0;
		else xx+=(ll)b[i].ad;
		if(xx<=0) return puts("NIE"),0;
	}
	puts("TAK");
	for(int i=1;i<=cnt;i++) printf("%d ",a[i].id);
	for(int i=1;i<=num;i++) printf("%d ",b[i].id);
	return 0;
}

\(43.\) P3044 [USACO12FEB]Relocation S

因为 \(k\) 小的可怜,所以考虑枚举全排列。

另外不能用超级源点(因为要保证起点终点相同,如果只去不回或只回不去就可以了),所以只能在枚举起点。

然后就是很裸的最短路了,时间复杂度为 \(\mathcal O((n+m)k\log n+nk!)\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
#include"vector"
#include"algorithm"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 50005
#define mem(s) memset(s,0,sizeof(s))
#define inf 2147483647

int n,m,k;
int x,y,z;
int shp[10];
struct node
{
	int to,w,nxt;
}e[MAXN<<1];
int head[10005],cnt=0;
int dis[10005],vis[10005];
int rt[10][10005];
int mark[10005];
int ans=inf;
int us[10];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q; 

void add(int u,int v,int w)
{
	e[++cnt].to=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

void dij()
{
	while(!q.empty())
	{
		int s=q.top().second;
		q.pop();
		if(vis[s]) continue;
		vis[s]=1;
		for(int i=head[s];i;i=e[i].nxt)
		{
			int j=e[i].to;
			if(dis[j]>dis[s]+e[i].w)
			{
				dis[j]=dis[s]+e[i].w;
				q.push(make_pair(dis[j],j));
			}
		}
	}
	return;
}

int main()
{
	read(n),read(m),read(k);
	for(int i=1;i<=k;i++) read(shp[i]),mark[shp[i]]++;
	for(int i=1;i<=m;i++)
	{
		read(x),read(y),read(z);
		add(x,y,z),add(y,x,z);
	}
	for(int i=1;i<=k;i++)
	{
		for(int j=1;j<=n;j++) dis[j]=inf,vis[j]=0;
		dis[shp[i]]=0,q.push(make_pair(0,shp[i]));
		dij();
		for(int j=1;j<=n;j++) rt[i][j]=dis[j];
	}
	for(int s=1;s<=n;s++)
	{
		if(mark[s]) continue;
		for(int i=1;i<=k;i++) us[i]=i;
		do
		{
			int sum=rt[us[1]][s]+rt[us[k]][s];
			for(int i=1;i<k;i++) sum+=rt[us[i]][shp[us[i+1]]];
			ans=min(ans,sum);
		}while(next_permutation(us+1,us+k+1));
	}
	printf("%d\n",ans);
	return 0;
}

\(44.\) P1966 火柴排队

感性想一下(然而这改变不了我不会这题的事实)也知道当都按顺序排列时或者相对火柴在各自盒中大小序相同时有最值。

然后把一个数组按另一数组大小序编号后的编号逆序对数就是最终答案,时间复杂度为 \(\mathcal O(n\log n)\)


\(45.\) P3957 跳房子

不知道为啥有单调性但是感觉要二分。

然后显然需要一种 \(\mathcal O(n)\)check 函数。

贪心是首选然而似乎不能贪。

考虑 dp ,比较显然。

\[dp_i=\max\limits_{j=l}^r dp_j+val_i \]

这里的 \(dp_i\) 代表到第 \(i\) 个有数格子可得的最大分数。

\(l,r\) 是在限制之内可到的区间端点。

发现 \(l,r\)\(i\) 变大单调不下降,所以用单调队列维护区间最大值。(用 st 表,线段树亦可,只是多一个 \(\log\))。

实现的好的话可以做到 \(\mathcal O(n\log \max x_i)\)

其实蛮好想的,然而没做出来,我脑子算废了。


\(46.\) P2966 [USACO09DEC]Cow Toll Paths G

一道很不错的题。

由于我脑子笨想不到如何在 Floyd 时动态维护每个路径最大点权值变化以及随即而来的排名变更。

又想到每个最优解只有一个最大权值点。

所以考虑枚举这个最大权值点。

枚举是有技巧的。

考虑从最小的点开始考虑,这样可以动态更新最大点权。

然后增添相关边,对于其相关的距离进行更新。

显然只有三种:起点,终点,中转点。

最后注意不完全更新,所以要跑两遍 Floyd 保证第一次漏下的全补上。

瓶颈时间复杂度 \(\mathcal O(n^3)\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
using namespace std;

#define read(x) scanf("%d",&x)
#define inf 1e9

int n,m,q;
struct node
{
	int id,val;
}a[255];
struct edge
{
	int to,nxt,w;
}e[10005<<1];
int head[255],cnt=0;
int dis[255][255]={0};
int x,y,z;
int st[255],top=0;
int ans[255][255];

bool cmp(node x,node y){return x.val<y.val;}

void add(int u,int v,int w)
{
	e[++cnt].to=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

int main()
{
	read(n),read(m),read(q);
	for(int i=1;i<=n;i++) read(a[i].val),a[i].id=i;
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) 
		{
			if(i^j) dis[i][j]=inf;
			else dis[i][j]=0;
			ans[i][j]=dis[i][j];
		}
	}
	for(int i=1;i<=m;i++)
	{
		read(x),read(y),read(z);
		add(x,y,z),add(y,x,z);
	}
	for(int i=1;i<=n;i++)
	{
		int u=a[i].id;
		for(int k=head[u];k;k=e[k].nxt)
		{
			int j=e[k].to;
			dis[j][u]=dis[u][j]=min(dis[u][j],e[k].w);
		}
		if(top>=2)
		{
			for(int j=1;j<=top;j++)
			{
				for(int k=1;k<=top;k++)
				{
					if(j==k) continue;
					int tj=st[j],tk=st[k];
					dis[u][tj]=min(dis[u][tj],dis[u][tk]+dis[tk][tj]);
					dis[tj][u]=min(dis[tj][u],dis[tj][tk]+dis[tk][u]);
					dis[tj][tk]=min(dis[tj][tk],dis[tj][u]+dis[u][tk]);	
				}
			}
			for(int j=1;j<=top;j++)
			{
				for(int k=1;k<=top;k++)
				{
					if(j==k) continue;
					int tj=st[j],tk=st[k];
					dis[u][tj]=min(dis[u][tj],dis[u][tk]+dis[tk][tj]);
					dis[tj][u]=min(dis[tj][u],dis[tj][tk]+dis[tk][u]);
					dis[tj][tk]=min(dis[tj][tk],dis[tj][u]+dis[u][tk]);	
				}
			}
		}
		st[++top]=u;
		for(int j=1;j<=top;j++)
		{
			for(int k=1;k<=top;k++)
			{
				int tj=st[j],tk=st[k];
				if(dis[tj][tk]==inf) continue;
				ans[tj][tk]=min(ans[tj][tk],dis[tj][tk]+a[i].val);
			}
		}
	}
	while(q--) read(x),read(y),printf("%d\n",ans[x][y]);
	return 0;
}

\(47.\) P1621 集合

冰茶姬即视感.jpg

枚举所有可能并可行的因数和在这个范围内的倍数用冰茶姬辅助合并最后统计集合数即可。


\(48.\) P2376 [USACO09OCT]Allowance G

贪心取大到接近的,超过或没了用小的补。

不用担心,暴力即可。


\(49.\) P1846 游戏

其实就是把每个数减一后得到的数组合而已,可以 dp ,设 \(dp_{i,j}\) 为第一个数组删了 \(i\) 个数,第二个数组删了 \(j\) 个数的最小分数。

有结论:越分散越好,那个火柴的题也是这样的。

故:

\[dp_{i,j}=\min\{dp_{i,j-1},dp_{i-1,j},dp_{i-1,j-1}\}+a_{n-i+1}\times b_{m-j+1} \]


\(50.\) P1272 重建道路

这是我第一次真真切切地理解树形背包。

其实就是一种特殊的有限制背包。

记住循环顺序:

先总容量,再子树容量,先逆序,后倒序(一般树形背包都是 \(01\) 背包模型) 。

时间复杂度好像挺玄学的,好像是完全跑不满的 \(\mathcal O(nP^2)\)

于是代码:

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 155 

int n,p;
int l,r;
struct node
{
	int to,nxt; 	
}e[MAXN<<1];
int head[MAXN],cnt=0;
int dp[MAXN][MAXN];
int son[MAXN],cntt[MAXN];
int ans=1e9;

void add(int u,int v)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

int dfs1(int cur,int fa)
{
	for(int i=head[cur];i;i=e[i].nxt)
	{
		int j=e[i].to;
		if(j!=fa) cntt[cur]++,son[cur]=son[cur]+dfs1(j,cur)+1;
	}
	return son[cur];
}

void dfs(int cur,int fa)
{
	if(!son[cur]) dp[cur][1]=1;
	if(cur==1) dp[cur][1]=cntt[cur];
	else dp[cur][1]=cntt[cur]+1;
	for(int i=head[cur];i;i=e[i].nxt)
	{
		int g=e[i].to;
		if(g==fa) continue;
		dfs(g,cur);
		for(int k=p;k>=1;k--)
		{
			for(int j=1;j<=son[g]+1&&k-j>0;j++)
			{

				dp[cur][k]=min(dp[cur][k],dp[cur][k-j]+dp[g][j]-2);	
			}
		}
	}
}

int main()
{
	read(n),read(p);
	for(int i=1;i<n;i++) read(l),read(r),add(l,r),add(r,l);
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=1e9;
	dfs1(1,1);
	dfs(1,1);
	for(int i=1;i<=n;i++) if(son[i]>=p-1) ans=min(ans,dp[i][p]);
	printf("%d\n",ans);
	return 0;
}

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

posted @ 2020-06-20 13:34  童话镇里的星河  阅读(227)  评论(0编辑  收藏  举报