……

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

为提高阅读质量防止博主更博时博客炸掉,所以这里只放 \(T_{51}\sim T_{100}\)

前情回顾:口胡(然而有代码)<第一章>

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

题目计数:\(100\)

\(51.\) P2706 巧克力

悬线法。

可以去这里学习,因为时间有限这里就不讲了。

利用二维前缀和和悬线法的 dp 思想可以做到 \(\mathcal O(n^2)\)


\(52.\) P4937 Portal1

有限制的背包问题,感觉有蹊跷一定要排序,因为排完序一定不会错!

由于我很傻不会继承选哪些点,所以写的 \(\mathcal O(n\log n+n^2\max d)\)

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

int n;
struct node
{
	int t,d,c,id;	
}a[105];
int dp[2005];
int maxn=0,ans=0,num;
int rt[2005][105];
int cnt=0;

bool cmp(node n,node m){return n.d<m.d;}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].t,&a[i].d,&a[i].c),maxn=max(maxn,a[i].d);
	for(int i=1;i<=n;i++) a[i].id=i;
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		for(int j=a[i].d-1;j>=a[i].t;j--)
		{
			if(dp[j-a[i].t]+a[i].c>dp[j])
			{
				dp[j]=dp[j-a[i].t]+a[i].c;
				for(int k=1;k<=n;k++) rt[j][k]=rt[j-a[i].t][k];
				rt[j][i]=1;
			}
		}
	}
	for(int i=1;i<maxn;i++) if(dp[i]>ans) ans=dp[i],num=i;
	printf("%d\n",ans);
	for(int i=1;i<=n;i++) if(rt[num][i]) cnt++;
	printf("%d\n",cnt);
	for(int i=1;i<=n;i++) if(rt[num][i]) printf("%d ",a[i].id);
	puts("");
	return 0;
}

\(53.\) P1607 [USACO09FEB]Fair Shuttle G

很套路(?)的一道贪心,即按结束位置排序然后贪心上最多的牛。

为什么按结束位置排序?

因为会留下最多的上牛机会。


\(54.\) P1016 旅行家的预算

比较经典的贪心。

麻烦在有好多种情况所以我就不写了。

这篇题解[Link]写得很清楚,去那里看吧。

我太懒了啊......


\(55.\) P4084 [USACO17DEC]Barn Painting G

一道不错的树形 \(dp\)

可以用结合律推一推式子,比如可以得到:

\[dp_{i,1}=\prod\limits_{j=ison} (dp_{j,2}+dp_{j,3}) \]

然后处理一下特殊情况即可。

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

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

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

int n,m;
int l,r;
struct node
{
	int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
ll dp[MAXN][5];
int rt[MAXN],top=0;
int lim[MAXN],op[MAXN][5];

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

void dfs(int cur,int fa)
{
	op[cur][1]=op[cur][2]=op[cur][3]=0;
	for(int i=head[cur];i;i=e[i].nxt)
	{
		int j=e[i].to;
		if(j==fa) op[cur][lim[j]]=1;
		else op[cur][lim[j]]=1,dfs(j,cur);
	}
	for(int i=head[cur];i;i=e[i].nxt)
	{
		int j=e[i].to;
		if(j!=fa) rt[++top]=j;
	}
	if(!lim[cur])
	{
		if(!op[cur][1])
		{
			ll now=1ll;
			for(int i=1;i<=top;i++)
			{
				now=(dp[rt[i]][2]+dp[rt[i]][3])%MOD*now%MOD;
			}
			dp[cur][1]=now%MOD;
		}
		else dp[cur][1]=0;
		if(!op[cur][2])
		{
			ll now=1ll;
			for(int i=1;i<=top;i++)
			{
				now=(dp[rt[i]][1]+dp[rt[i]][3])%MOD*now%MOD;
			}
			dp[cur][2]=now%MOD;
		}
		else dp[cur][2]=0;
		if(!op[cur][3])
		{
			ll now=1ll;
			for(int i=1;i<=top;i++)
			{
				now=(dp[rt[i]][1]+dp[rt[i]][2])%MOD*now%MOD;
			}
			dp[cur][3]=now%MOD;
		}
		else dp[cur][3]=0;
	}
	else
	{
		if(lim[cur]==1)
		{
			ll now=1ll;
			for(int i=1;i<=top;i++)
			{
				now=(dp[rt[i]][2]+dp[rt[i]][3])%MOD*now%MOD;
			}
			dp[cur][1]=now%MOD;
			dp[cur][2]=dp[cur][3]=0;
		}
		if(lim[cur]==2)
		{
			ll now=1ll;
			for(int i=1;i<=top;i++)
			{
				now=(dp[rt[i]][1]+dp[rt[i]][3])%MOD*now%MOD;
			}
			dp[cur][2]=now%MOD;
			dp[cur][1]=dp[cur][3]=0;
		}
		if(lim[cur]==3)
		{
			ll now=1ll;
			for(int i=1;i<=top;i++)
			{
				now=(dp[rt[i]][1]+dp[rt[i]][2])%MOD*now%MOD;
			}
			dp[cur][3]=now%MOD;
			dp[cur][1]=dp[cur][2]=0;
		}
	}
	top=0;
	return;
}

int main()
{
	read(n),read(m);
	for(int i=1;i<n;i++) read(l),read(r),add(l,r),add(r,l);
	for(int i=1;i<=m;i++) read(l),read(r),lim[l]=r;
	dfs(1,0);
	printf("%lld\n",(dp[1][1]+dp[1][2]+dp[1][3])%MOD);
	return 0;
}

\(56.\) P3668 [USACO17OPEN]Modern Art 2 G

观察一下合法情况都是先开始后结束,和栈一样,所以用栈维护一下即可。

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


\(57.\) P1383 高级打字机

恭喜半退役选手 tlx 读错题目!

所以直接主席树即可。

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


\(58.\) P1772 [ZJOI2006]物流运输

先预处理出每个状态下的最短路和每个最短路经过节点的情况,我会做到 \(\mathcal O(nm^2\log m)\)

然后设 \(dp_{i,j}\) 为第 \(i\) 天走第 \(j\) 天最短路是的最小路费,那么:

\[dp_{i,j}=\min\{dp_{i-1,j}+s_j,dp_{i-1,k}+s_k(j \not=k)+K\} \]

然后就是一个 \(\mathcal O(nm^2\log m+nm)\) 的实现了。


\(59.\) P2950 [USACO09OPEN]Bovine Embroidery G

考虑求出交点,然后把圆从某个点展开,离散化一下,就是区间交集个数总和了!

可以用树状数组或平衡树维护,时间复杂度是 \(\mathcal O(n\log n)\)


\(60.\) P2034 选择数字

练一下单调队列优化 \(dp\) ,这里要留一个单调队列头权值为 \(0\) 做辅助。

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

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

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

int n,k;
int a[MAXN];
int head=1,tail=1;//head=1留白一个0 
ll q[MAXN];
int loc[MAXN];
ll dp[MAXN];
ll sum=0;
ll ans=1ll<<62;

int main()
{
	read(n),read(k); 
	for(int i=1;i<=n;i++)
	{
		read(a[i]);
		sum=sum+(ll)a[i];
		dp[i]=q[tail]+a[i];
		while(head>tail&&q[head]>=dp[i]) head--;
		q[++head]=dp[i],loc[head]=i;
		if(loc[tail]<i-k) tail++; 
	}
	for(int i=n;i>=n-k;i--) ans=min(ans,dp[i]);
	printf("%lld\n",sum-ans);
	return 0;
}

\(61.\) P2216 [HAOI2007]理想的正方形

还是单调队列问题。

单调队列套单调队列。

我们先维护一下从某个点出发的 \(1 \times n\) 得矩形的最大值/最小值。

然后在更新最值正方形的时候就可用 \(1\times n\) 的最值来代表一条线了。

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

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

#define ll long long
#define MAXN 1005

int a,b,n;
ll q1[MAXN],loc1[MAXN];
ll q2[MAXN],loc2[MAXN];
ll x[MAXN][MAXN];
int head1,tail1;
int head2,tail2;
ll maxn[MAXN][MAXN],minn[MAXN][MAXN];
ll ans=1ll<<62;

int main()
{
	scanf("%d%d%d",&a,&b,&n);
	for(int i=1;i<=a;i++)
	{
		for(int j=1;j<=b;j++) scanf("%lld",&x[i][j]);
	}
	for(int j=1;j<=a;j++)
	{
		head1=0,tail1=1;
		memset(q1,0,sizeof(q1)),memset(loc1,0,sizeof(loc1));
		for(int i=1;i<=b;i++)
		{
			while(head1>=tail1&&q1[head1]<=x[j][i]) head1--;
			q1[++head1]=x[j][i],loc1[head1]=i;
			if(loc1[tail1]<i-n+1) tail1++;
			if(i>=n) maxn[j][i-n+1]=q1[tail1];
		}
	}
	for(int j=1;j<=a;j++)
	{
		head1=0,tail1=1;
		memset(q1,0,sizeof(q1)),memset(loc1,0,sizeof(loc1));
		for(int i=1;i<=b;i++)
		{
			while(head1>=tail1&&q1[head1]>=x[j][i]) head1--;
			q1[++head1]=x[j][i],loc1[head1]=i;
			if(loc1[tail1]<i-n+1) tail1++;
			if(i>=n) minn[j][i-n+1]=q1[tail1];
		}
	}
	for(int j=1;j+n-1<=b;j++)
	{
		head1=0,tail1=1;
		memset(q1,0,sizeof(q1)),memset(loc1,0,sizeof(loc1));
		head2=0,tail2=1;
		memset(q2,0,sizeof(q2)),memset(loc2,0,sizeof(loc2));
		for(int i=1;i<=a;i++)
		{
			while(head1>=tail1&&minn[i][j]<=q1[head1]) head1--;
			q1[++head1]=minn[i][j],loc1[head1]=i;
			if(loc1[tail1]<i-n+1) tail1++;
			while(head2>=tail2&&maxn[i][j]>=q2[head2]) head2--;
			q2[++head2]=maxn[i][j],loc2[head2]=i;
			if(loc2[tail2]<i-n+1) tail2++;
			if(i>=n) ans=min(ans,q2[tail2]-q1[tail1]);
		} 
	}
	printf("%lld\n",ans);
	return 0;
} 

\(62.\) P3800 Power收集

单调队列优化 dp,直接维护一个长度为 \(2t+1\) 的窗口然后特判窗口小的情况即可。

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

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

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

int n,m,t,k;
int a[4005][4005];
int l,r,v;
int dp[4005][4005];
int q[4005],loc[4005],head=0,tail=1;
int ans=0;

int main()
{
    read(n),read(m),read(k),read(t);
    for(int i=1;i<=k;i++)
    {
        read(l),read(r),read(v);
        a[l][r]=v;
    }
    for(int i=1;i<=m;i++) dp[1][i]=a[1][i];
    if(m<=t+1)
    {
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            int now=0;
            for(int j=1;j<=m;j++) now=max(now,a[i][j]);
            ans+=now;
        }
        printf("%d\n",ans);
        return 0;
    }
    for(int i=2;i<=n;i++)
    {
        memset(q,0,sizeof(q)),memset(loc,0,sizeof(loc));
        head=0,tail=1;
        for(int j=1;j<=m;j++)
        {
            while(head>=tail&&q[head]<=dp[i-1][j]) head--;
            q[++head]=dp[i-1][j],loc[head]=j;
            if(loc[tail]<j-2*t) tail++;
            if(j>=t+1) dp[i][j-t]=q[tail]+a[i][j-t];
        }
        for(int j=1;j<=t;j++)
        {
            if(loc[tail]<m-t-t+j) tail++;
            dp[i][m-t+j]=q[tail]+a[i][m-t+j];
        }
    }
    for(int i=1;i<=m;i++) ans=max(ans,dp[n][i]);
    printf("%d\n",ans);
    return 0;
}

\(63.\) P5676 [GZOI2017]小z玩游戏

我们考虑把一个点的所有倍数和他连起来这样的复杂度仅仅是 \(\mathcal O(n\ln n)\) 的。

然后把所有联系的连起来,只需要看一下 \(w,e\) 是否在一个强联通分量里就好了。


\(64.\) P1262 间谍网络

考虑缩点。

最后有贡献的只能是入读为零的点,对缩完后的点取最小价值即可。

我很菜写了一个 \(\mathcal O(pn+m)\) 的算法。

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

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

int n,p,m;
int id=0,x,y;
int low[MAXN],num[MAXN];
int a[MAXN];
struct node
{
	int to,nxt;
}e[MAXN];
int head[MAXN],cnt=0;
int be[MAXN],si[MAXN],c=0;
int st[MAXN],top=0,vis[MAXN];
int val[MAXN];
int in[MAXN];
int l[MAXN],r[MAXN];
int ans=0;

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

void dfss(int cur)
{
	vis[cur]=1;
	for(int i=head[cur];i;i=e[i].nxt) if(!vis[e[i].to]) dfss(e[i].to);
	return;
}

void dfs(int cur)
{
	low[cur]=num[cur]=++id;
	vis[cur]=1,st[++top]=cur;
	for(int i=head[cur];i;i=e[i].nxt)
	{
		int j=e[i].to;
		if(!vis[j]) dfs(j);
		if(!be[j]) low[cur]=min(low[cur],low[j]);
	}
	if(low[cur]==num[cur])
	{
		c++;
		while(1)
		{
			int k=st[top--];
			be[k]=c,si[c]++,val[c]=min(val[c],a[k]);
			if(k==cur) break;
		}
	}
}

int main()
{
	read(n),read(p);
	for(int i=1;i<=n;i++) a[i]=val[i]=0x7fffffff;
	for(int i=1;i<=p;i++) read(x),read(y),a[x]=y;
	read(m);
	for(int i=1;i<=m;i++) read(l[i]),read(r[i]),add(l[i],r[i]);
	for(int i=1;i<=n;i++) if(!vis[i]&&a[i]<0x7fffffff) dfss(i);
	for(int i=1;i<=n;i++) if(!vis[i]) return printf("NO\n%d\n",i),0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);
	memset(e,0,sizeof(e)),memset(head,0,sizeof(head)),cnt=0;
	for(int i=1;i<=m;i++) if(be[l[i]]^be[r[i]]) add(be[l[i]],be[r[i]]),in[be[r[i]]]++;
	for(int i=1;i<=c;i++) if(!in[i]) ans+=val[i];
	printf("YES\n%d\n",ans);
	return 0;
}

\(65.\) P5960 【模板】差分约束算法

因为 \(dis_j<dis_i+w_{i to j}\) 实现所有最短路的充要条件,所有这样构图然后最短路即可。

啥时候无解?存在负环。

提醒:队列的空间要开大,大概要有 \(n^2\) 的最坏空间,所以建议用 STL 的队列。

时间复杂度 \(\mathcal O(kn)\)

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

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

int n,m;
int x,y,z;
struct node
{
	int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int dis[MAXN],vis[MAXN];
int cou[MAXN]={0},que[MAXN*MAXN],he=1,ta=1;

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 SPFA()
{
	que[1]=0,vis[0]=1,dis[0]=0;
	while(he>=ta)
	{
		int u=que[ta++];
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int j=e[i].to;
			if(dis[j]>dis[u]+e[i].w)
			{
				dis[j]=dis[u]+e[i].w,cou[j]=cou[u]+1;
				if(!vis[j]) que[++he]=j,vis[j]=1;
				if(cou[j]>=n+2){puts("NO");return;}
			}	
		}	
	}
	for(int i=1;i<=n;i++) printf("%d ",dis[i]);
	puts("");
	return;
}

int main()
{
	read(n),read(m);
	for(int i=1;i<=m;i++) read(x),read(y),read(z),add(y,x,z);
	for(int i=1;i<=n;i++) add(0,i,0);
	for(int i=1;i<=n;i++) dis[i]=1e9;
	SPFA();
	return 0;
}

\(66.\) P3275 [SCOI2011]糖果

差分约束练手题。

A不了。。。。。

最短路是错的,需要最长路,然后对于符号相反的约束移一下项就可以了。

把非等于化成可以等于的,可以加减 \(1\)


\(67.\) P2294 [HNOI2005]狡猾的商人

冷静分析发现只有出现环才会错。

所以把没在环上的先踢出去,然后枚举每个没走过的边,搜的时候尽量搜没走过的边,然后判断一下次环是否合法即可。

有一个不合法的就不行。


\(68.\) P6832 [Cnoi2020]子弦

水一波月赛签到题/cy

最多子串一定是一个字母。


\(69.\) P1682 过家家

一个男生同时能和多个女生搞(引发社会恐慌

并查集一下找能玩男生最少的集合能玩几个,加上 \(k\) 然后和 \(n\)\(\min\) 即可。


\(70.\) P2323 [HNOI2006]公路修建问题

这玩意好像叫最小瓶颈树来着。(好像不是,别听我的啊

先取大边,如果一样优先选小边最大的。

然后取小边,可以证明大边取的越少越好,然后直接最小生成树,复杂度 \(\mathcal O(n\log m)\)

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

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

int n,m,k;
int maxn=0;
struct node
{
	int l,r,v,w,u,id;
	node(){u=0;}
}a[MAXN];
int rt[MAXN],f[MAXN];
int len=0;

bool cmp1(node n,node m)
{
	if(n.v==m.v) return n.w>m.w;
	else return n.v<m.v;
}

bool cmp2(node n,node m)
{
	if(n.u==m.u) return n.w<m.w;
	else return n.u<m.u;
}

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

int merge(int u,int v)
{
	int t1=getf(u),t2=getf(v);
	if(t1==t2) return 0;
	f[t2]=t1;
	return 1;
}

int main()
{
	read(n),read(k),read(m);
	m-=1;
	for(int i=1;i<=m;i++) read(a[i].l),read(a[i].r),read(a[i].v),read(a[i].w),a[i].id=i;
	for(int i=1;i<=n;i++) f[i]=i;
	sort(a+1,a+m+1,cmp1);
	//for(int i=1;i<=m;i++) cout<<a[i].id<<endl;
	for(int i=1;i<=m;i++)
	{
		if(merge(a[i].l,a[i].r))
		{
			maxn=max(maxn,a[i].v);
			len++;
			a[i].u=1;
			rt[a[i].id]=1;
			if(len==k) break;
		}
	}
	sort(a+1,a+m+1,cmp2);
	for(int i=1;i<=m;i++)
	{
		if(merge(a[i].l,a[i].r))
		{
			maxn=max(maxn,a[i].w);
			len++;
			rt[a[i].id]=2;
			if(len==n-1) break;
		}
	}
	printf("%d\n",maxn);
	for(int i=1;i<=m;i++) 
	{
		if(!rt[i]) continue;
		printf("%d %d\n",i,rt[i]);
	}
	return 0;
}

\(71.\) P2638 安全系统

其实一个空位可以放多个数。

设到第 \(i\) 个位置放了 \(j\)\(0\),\(k\)\(1\),那么:

\[dp_{i,g,h}=\sum_{g>j,h>k}dp_{i-1,j,k} \]

由于特殊数据范围,复杂度可以做到 \(\mathcal O(nab)\)

坑,要开 ull,可以学习一下占位符...

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

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

ll dp[MAXN][MAXN][MAXN];
int n,a,b;
ll sum=0;

int main()
{
	read(n),read(a),read(b);
	dp[0][0][0]=1ull;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=a;j++)
		{
			for(int k=0;k<=b;k++)
			{
				for(int g=j;g<=a;g++)
				{
					for(int h=k;h<=b;h++)
					{
						dp[i][g][h]+=dp[i-1][j][k];
					}
				}
			}
		}
	}
	for(int i=0;i<=a;i++) for(int j=0;j<=b;j++) sum=sum+dp[n][i][j];
	printf("%llu\n",sum);
	return 0;
}

\(72\) P3545 [POI2012]HUR-Warehouse Store

考虑贪心。

发现需求小的顾客更合算,那就按需求从小到大排序,然后判断一下是否能满足。

发现可以用前缀和表示一下库存,然后显然是一个资瓷区间减与最小值的线段树。

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

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

#define MAXN 250005
#define read(x) scanf("%lld",&x)
#define ll long long

int n,bo[MAXN],cnt=0;;
ll sum[MAXN],x;
struct P
{
	ll val;
	int id;
}b[MAXN];
struct node
{
	ll rec,lzy;
	int l,r;
	node(){rec=lzy=0;l=r=0;}
}a[MAXN<<2];

bool cmp(P n,P m){return n.val<m.val;}

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

void build(int k,int l,int r)
{
	a[k].l=l,a[k].r=r;
	if(l==r){a[k].rec=sum[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)
{
	a[k<<1].lzy+=a[k].lzy,a[k<<1|1].lzy+=a[k].lzy;
	a[k<<1].rec-=a[k].lzy,a[k<<1|1].rec-=a[k].lzy;
	a[k].lzy=0;
}

void modify(int k,int l,int r,ll x)
{
	if(a[k].l==l&&a[k].r==r){a[k].lzy+=x,a[k].rec-=x;return;}
	int mid=(a[k].l+a[k].r)>>1;
	if(r<=mid) modify(k<<1,l,r,x);
	else if(l>mid) modify(k<<1|1,l,r,x);
	else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
	update(k);
}

ll query(int k,int l,int r)
{
	if(a[k].l==l&&a[k].r==r) return a[k].rec;
	lazydown(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 return min(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) read(x),sum[i]=sum[i-1]+x;
	for(int i=1;i<=n;i++) read(b[i].val),b[i].id=i;
	sort(b+1,b+n+1,cmp),build(1,1,n);
	for(int i=1;i<=n;i++)
	{
		ll now=query(1,b[i].id,n);
		if(b[i].val<=now) bo[b[i].id]=++cnt,modify(1,b[i].id,n,b[i].val);
	}
	printf("%d\n",cnt);
	for(int i=1;i<=n;i++) if(bo[i]) printf("%d ",i);
	return puts(""),0;
}

\(73\) P1651 塔

其实这玩意无后效性还是挺明显的。

我们记 \(dp_i\) 为右边比左边高 \(i\) 时右边最高为多少。

有负数下标,所以平移一下。

不能随时更新,所以把可行状态先记录一下,然后赋值,时间复杂度为 \(\mathcal O(n\sum h)\)

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

#define read(x) scanf("%d",&x)
#define MAXN 55
#define B 500002

int n;
int a[MAXN],dp[1000005];
int d[1000005];

int main()
{
	read(n);
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<=B*2;i++) dp[i]=-1;
	dp[B-a[1]]=dp[B]=0,dp[B+a[1]]=a[1];
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<=B*2;j++) d[j]=dp[j];
		for(int j=a[i];j<=B*2-a[i];j++)
		{
			if(dp[j]!=-1) 
			{
				d[j-a[i]]=max(d[j-a[i]],dp[j]);
				d[j+a[i]]=max(d[j+a[i]],dp[j]+a[i]);
			}
		}
		for(int j=1;j<=2*B;j++) dp[j]=max(dp[j],d[j]);
	}
	if(dp[B]>0) printf("%d\n",dp[B]);
	else printf("-1\n");
	return 0;
} 

\(74\) P5686 [CSP-SJX2019]和积和

黄题都不会做了/kk

冷静模拟一下,可以发现用一种分治思想,对于 \(a_i\)

分开考虑下标比 \(i\) 小的,和其他的,发现系数很有规律,而且好递推。

具体式子我就不写了,逃~

可以做到 \(\mathcal O(n)\)

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

#define ll long long
#define MAXN 500005
#define MOD 1000000007

ll a[MAXN],b[MAXN],ans=0;
int n;
ll now=0;
ll ba=0;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
	for(int i=1;i<=n;i++) now=(now+(n-i+1)*b[i]%MOD)%MOD;
	for(int i=1;i<=n;i++)
	{
		ans=(ans+((n-i+1)*ba%MOD+i*(now)%MOD)*a[i]%MOD%MOD)%MOD;
		ba=(ba+i*b[i]%MOD)%MOD;
		now=(now-(n-i+1)*b[i]%MOD+MOD)%MOD;
	}
	printf("%lld\n",ans%MOD);
	return 0;
} 

\(75\) P2597 [ZJOI2012]灾难

问题很难考虑

毕竟是 ZJOI 的题吗(可这还是蓝题啊喂

反向建边,然后向下拓扑排序,发现如果一个点连很多边,那么只有灭绝他所有父亲的 LCA 才可,所以连到 LCA 上,当然这保证了上边是一棵树。

最后答案就是子树大小减去自己。


\(76\) P1188 PASTE

发现要输出的数很少,所以枚举每一个数,逆推过程找一下他的本源。


\(77\) CF873C Strange Game On Matrix

随便做就好了(bushi

前缀和维护一下 1 的个数,然后算每个区间


\(78\) CF631C Report

考虑大区间珂以覆盖前面的所有比他小的区间,所以每次维护尽可能最大的区间的空隙即可。

能做到 \(\mathcal O(m+n\log n)\)

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

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

int n,m;
int b[MAXN],a[MAXN];
int num[MAXN];
struct node
{
	int t,v,id;	
}op[MAXN],r[MAXN];
int u[MAXN]={0},maxn=1,rn;
int l,ri;
int lst;

bool cmp(node n,node m){if(n.v==m.v) return n.id>m.id;else return n.v>m.v;}

int main()
{
	read(n),read(m);
	for(int i=1;i<=n;i++) read(b[i]);
	for(int i=1;i<=m;i++) read(op[i].t),read(op[i].v),op[i].id=i,r[i]=op[i];
	sort(op+1,op+m+1,cmp),rn=n;
	for(int i=1;i<=m;i++) r[op[i].id].id=i;
	for(int i=1;i<=m;i++)
	{
		u[r[i].id]=1;
		if(r[i].id==maxn)
		{
			if(maxn==1)
			{
				for(int j=r[i].v+1;j<=rn;j++) num[j]=b[j];
				for(int j=1;j<=r[i].v;j++) a[j]=b[j];
				sort(a+1,a+r[i].v+1);
				l=1,ri=r[i].v;
				rn=r[i].v;
			}
			else
			{
				if(lst==2)
				{
					for(int j=1;j<=rn-r[i].v;j++) num[rn-j+1]=a[l+j-1];
					l=l+rn-r[i].v;
				}
				else
				{
					for(int j=1;j<=rn-r[i].v;j++) num[rn-j+1]=a[ri-j+1];
					ri=ri-rn+r[i].v;
				}
				lst=r[i].t;
				rn=r[i].v;
			}
			lst=r[i].t;
			while(u[maxn]&&maxn<=n) maxn++;
		}
	}
	if(lst==1) for(int i=l;i<=ri;i++) num[i-l+1]=a[i];
	else for(int i=ri;i>=l;i--)num[ri-i+1]=a[i];
	for(int i=1;i<=n;i++) printf("%d ",num[i]);
	return puts(""),0;
}

\(79\) CF598E Chocolate Bar

挺不错的一道 dp。

考虑依题意设出 \(dp_{i,j,k}\)

然后有:

\[dp_{n,m,k}=\min\{dp_{i-s,j,k-q}+dp_{s,j,q}+j^2,dp_{i,j-s,k-q}+dp_{i,s,q}+i^2\} \]

注意一下边界。

你发现要五重循环,然后 \(k\) 的取值范围保证时间复杂度是 \(\mathcal O(nm(n+m)k^2+t)\) 并不会超时。

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

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

int dp[35][35][55];
int t,n,m,k;

int main()
{
	read(t);
	dp[1][1][1]=0;
	dp[1][1][0]=0;
	for(int i=1;i<=31;i++)
	{
		for(int j=1;j<=31;j++)
		{
			if(i==1&&j==1) continue;
			int op=min(i*j,52);
			for(int k=1;k<op;k++)
			{
				dp[i][j][k]=100000000;
				for(int s=1;s<=(i/2)+1&&i-s>=0;s++)
				{
					for(int q=0;q<=s*j&&q<=k;q++)
					{
						if((k-q)>(i-s)*j) continue;
						dp[i][j][k]=min(dp[i][j][k],dp[i-s][j][k-q]+dp[s][j][q]+j*j);
					}
				}
				for(int s=1;s<=(j/2)+1&&j-s>=0;s++)
				{
					for(int q=0;q<=i*s&&q<=k;q++)
					{
						if(k-q>i*(j-s)) continue;
						dp[i][j][k]=min(dp[i][j][k],dp[i][j-s][k-q]+dp[i][s][q]+i*i);
					}
				}
			}
		}
	}
	while(t--)
	{
		read(n),read(m),read(k);
		printf("%d\n",dp[n][m][k]);
	}
	return 0;
}

\(80\) P5022 旅行

对于树的部分分,只要看懂题就随便做了是不是?

60 分,ccf 感人放送。

对于基环树的部分,我们由题意模拟一下发现有一条在环上的边是不用走的,所以暴力枚举被删掉的边,然后一样做就好了。


\(81\) P5021 赛道修建

菜菜菜

考虑贪心。

每一个子树都期望匹配到最多的合法赛道,所以在二分答案的同时,每次向下 DFS 在子树内匹配,再将无法匹配到的最大的边(想想为什么)传上去加到上面的贡献,最后判断是否有这些赛道可修建就好了,时间复杂度是 \(\mathcal O(n\log^2n)\)


\(82\) P2674 《瞿葩的数字游戏》T2-多边形数

找规律好题

我们观察下表发现第 \(i\)\(k+2\) 边形数为 \(k\dfrac{i(i-1)}{2}+i\)

我们首先考虑把 \(i\) 解出来:

化简可知:

\[i=\dfrac{k-2+\sqrt{k^2+(8N-4)k+4}}{2k} \]

想法 \(1:\)

首先保证根号下是整数,然后在保证相除仍为整数。

具体做法?

枚举 \(k\),然而易知 \(k_{max}\)\(\mathcal O(N)\) 级别的,会炸掉。

想法 \(2:\)

直接枚举 \(i\) 的取值,

由于三角形数通项公式很容易得到是:

\[S_i=\dfrac{i(i+1)}{2} \]

作为最小的多边形数,\(i\) 的取值就被限制在 \(\mathcal O(\sqrt{N})\) 级别内,不会炸掉。

更进一步来说,我们与其用 \(k\) 来表示 \(i\) ,不如用 \(i\) 表示 \(k\)

易得:

\[k=\dfrac{2(N-i)}{i(i-1)}\;\;\;(i>2) \]

当然 \(i=1\) 的情况(即 \(N=1\) )我们可以特判一下。

为了容易比较我们可以计算该函数的单调性,对其求导可知:

\[k'=\dfrac{-2i(i-1)-2(N-i)(2i-1)}{(i(i-1))^2}<0 \]

易知该函数单调递减,所以从大(大概是从 \(\mathcal O(\sqrt{2n})\) 左右开始枚举,注意始终保证 \(N-i>0\))到小(不能取 \(1\))枚举 \(i\)找到两个合法的就行了。

总的时间复杂度是 \(\mathcal O(T\sqrt{N})\)

\(\mathcal {Code:}\)(可能略微难看,见谅)

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

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

int n,x;
int k1,k2;

int main()
{
	read(n);
	while(n--)
	{
		k1=0,k2=0;
		read(x);
		if(x==1){printf("3 4\n");continue;}
		for(int i=sqrt(2*x)+1;i>=2;i--)
		{
			if(x<=i) continue;
			if(2*(x-i)%(i*(i-1))==0)
			{
				if(!k1) k1=2*(x-i)/(i*(i-1))+2;
				else
				{
					k2=2*(x-i)/(i*(i-1))+2;
					break;
				}
			}
		}
		if(!k1) printf("Poor%d\n",x);
		else if(!k2) printf("%d\n",k1);
		else printf("%d %d\n",k1,k2);
	}
	return 0;
} 

\(83\) P1438 无聊的数列

由于是单点查询所以线段树很好搞,具体的话就是差分。

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

#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,m;
struct node
{
	int l,r;
	ll sum,lzy;
	node(){l=r=0;sum=lzy=0;}
}a[MAXN<<2];
int opt,l,r,K,D;
int rt[MAXN],b[MAXN];

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

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

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

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

ll query(int k,int l,int r)
{
	if(a[k].lzy) lazydown(k);
	if(a[k].l==l&&a[k].r==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()
{
	read(n),read(m);
	for(int i=1;i<=n;i++) read(rt[i]);
	b[1]=rt[1];
	for(int i=2;i<=n;i++) b[i]=rt[i]-rt[i-1];
	build(1,n,1);
	while(m--)
	{
		read(opt);
		if(opt==1)
		{
			read(l),read(r),read(K),read(D);
			modify(1,l,l,(ll)K);
			if(l!=r) modify(1,l+1,r,(ll)D);
			if(r<n) modify(1,r+1,r+1,(ll)(-K-D*(r-l)));
		}
		else 
		{
			read(r);
			printf("%lld\n",query(1,1,r));
		}
	}
	return 0;
}

\(84\) P2215 [HAOI2007]上升序列

做题发现自己不会 \(\mathcal O(n\log n)\) \(LIS\) 了,怎么办?看题解!

线段树!

我们考虑从后向前枚举每一个数,如果设 \(dp_i\) 为以 \(i\) 开头的最长下降子序列的长度,那么只需要求得在他后面比他大的数的 \(dp\) 值的最大值。
所以我们考虑将权值离散化,将没有扫过(显然这些数在现在这个数前面)的数的 \(dp\) 值设得很小,然后线段树上二分求一下最大值就好了了。

时间复杂度就是常数比较大的 \(\mathcal O(n\log n)\)

对于询问,考虑贪心即可。

由于数据是随机的,这个 \(\mathcal O(nm)\) 的询问复杂度也不会跑得很满,所以就很轻松的过掉了。

参考代码:

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

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

struct num
{
	int val,id;
}b[MAXN];
int n,m;
struct node
{
	int l,r,rec;
}a[MAXN<<2];
int re[MAXN],dp[MAXN];
int l,me[MAXN];

bool cmp(num n,num m){if(n.val==m.val) return n.id>m.id;else return n.val<m.val;}

void hash()
{
	sort(b+1,b+n+1,cmp);
	for(int i=1;i<=n;i++) re[b[i].id]=i,dp[i]=-1;
}

void update(int k){a[k].rec=max(a[k<<1].rec,a[k<<1|1].rec);}

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

void modify(int k,int x,int y)
{
	if(a[k].l==a[k].r){a[k].rec=y;return;}
	if(a[k<<1].r>=x) modify(k<<1,x,y);
	else modify(k<<1|1,x,y);
	update(k);
}

int query(int k,int l,int r)
{
	if(a[k].l==l&&a[k].r==r) return a[k].rec;
	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 max(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}

int main()
{
	read(n);
	for(int i=1;i<=n;i++) read(b[i].val),me[i]=b[i].val,b[i].id=i;
	hash(),build(1,1,n);
	for(int i=n;i>=1;i--)
	{
		int now=query(1,re[i],n);
		if(now==-1) dp[i]=1;
		else dp[i]=now+1;
		modify(1,re[i],dp[i]);
	}
	read(m);
	while(m--)
	{
		read(l);
		int now=l,lst=-0x7fffffff;
		for(int i=1;i<=n;i++)
		{
			if(dp[i]>=now&&me[i]>lst)
			{
				now--,lst=me[i];
				printf("%d ",me[i]);
				if(now==0) break;
			}
		}
		if(now>0) printf("Impossible");
		puts("");
	}
	return 0;
}

\(85\) P3243 [HNOI2015]菜肴制作

这大概是正难则反的典例了吧。

考虑反向建图,这时候只需要让编号小的数靠后出现,所以用大根堆来维护待排序的点,将逆拓扑序列翻转即求得答案。

对于出现环(具体表示为无法进堆)的情况,输出 "Impossible!" 即可。

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

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

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

struct node
{
	int to,nxt;
}e[MAXN];
int head[MAXN],cnt=0;
int n,m,t;
int x,y;
int ind[MAXN];
priority_queue<int> q;
int rt[MAXN],c=0;
int vis[MAXN];

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

int main()
{
	read(t);
	while(t--)
	{
		mem(e),mem(head),mem(ind),mem(rt),mem(vis);
		cnt=0,c=0;
		read(n),read(m);
		for(int i=1;i<=m;i++) read(x),read(y),add(y,x),ind[x]++;
		for(int i=1;i<=n;i++) if(!ind[i]) q.push(i);
		if(q.empty()){printf("Impossible!\n");continue;}
		while(!q.empty())
		{
			int u=q.top();
			q.pop();
			rt[++c]=u;
			for(int i=head[u];i;i=e[i].nxt)
			{
				int j=e[i].to;
				if(vis[j]) continue;
				ind[j]--;
				if(!ind[j]) vis[j]=1,q.push(j);
			}
		}
		if(c!=n){printf("Impossible!\n");continue;}
		for(int i=n;i>=1;i--) printf("%d ",rt[i]);
		puts("");
	}
	return 0;
}

\(86\) P2549 计算器写作文

超超超级恶心的一道字符串题目,虽然只考察字符串基本操作。

题目很是简单,发现是背包这个东西有序啊!

贪心很容易,所以按拼接后大小排序即可。

不精细操作可以做到 \(\mathcal O(nD^2)\)。(竟然稳过?)

注意小数处理较为恶心。

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

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

string dp[205],maxn;
int vis[205];
int D,n;
char c[35],e[205];
string s[10005];
int len[10005];
int flag=0;

int tran(char c)
{
	int x;
	if(c=='O'||c=='D') x=0;
	else if(c=='G') x=9;
	else if(c=='B') x=8;
	else if(c=='L') x=7;
	else if(c=='q') x=6;
	else if(c=='S') x=5;
	else if(c=='h') x=4;
	else if(c=='E') x=3;
	else if(c=='Z') x=2;
	else if(c=='I') x=1;
	return x;
}

bool cmp(string s,string t)
{
	int l=s.size();
	for(int i=l-1;i>=0;i--)
	{
		int op=tran(s[i]),rt=tran(t[i]);
		if(op>rt) return 1;
		else if(op<rt) return 0;
	}
	return 0;
}

bool cmppp(string a,string b)//p 太多辣! 
{
	int lena=a.size(),lenb=b.size();
	for(int i=lena-1;i>=0;i--)
	{
		int op=tran(a[i]),rt=tran(b[i-lena+lenb]);
		if(op>rt) return 1;
		else if(op<rt) return 0;
	}
	return 0;
}

bool cmpp(string a,string b)
{
	if(cmp(a+b,b+a)) return 1;
	else return 0;
}

int main()
{
	read(D),read(n);
	for(int i=1;i<=n;i++) scanf("%s",c),s[i]=c;
	sort(s+1,s+n+1,cmpp);
	for(int i=1;i<=n;i++) len[i]=s[i].size();
	for(int i=1;i<=D;i++)
	{
		for(int j=0;j<i;j++) e[j]='O';
		dp[i]=e;
	}
	vis[0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=D-len[i];j>=0;j--)
		{
			if(!vis[j]) continue;
			vis[j+len[i]]=1;
			string o=dp[j],t=s[i],ans;
			if(o=="") ans=s[i];
			else
			{
				string ss=o+t,tt=t+o;
				if(cmp(ss,tt)) ans=ss;
				else ans=tt;	
			}
			if(cmp(ans,dp[j+len[i]])) dp[j+len[i]]=ans;
		}
	}
	int k;
	for(k=D;k>=0;k--) if(vis[k]) break;
	if(dp[k][k-1]=='O'||dp[k][k-1]=='D')
	{
		if(k==1) return puts("0"),0;
		string ans=dp[k];
		int lenn=k;
		for(int i=k;i>=1;i--)
		{
			if(cmppp(dp[i],ans)) ans=dp[i],lenn=i;
		}
		printf("0.");
		for(int i=lenn-2;i>=0;i--) printf("%d",tran(ans[i]));
		puts("");
	}
	else
	{
		for(int i=k-1;i>=0;i--) printf("%d",tran(dp[k][i]));
		puts("");
	}
	return 0;
}

\(87\) P1186 玛丽卡

考虑删去最短路上的每一条边,然后跑最短路,取一遍 \(\max\) 就好了。

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


\(88\) P2915 [USACO08NOV]Mixed Up Cows G

数据范围真就状压既视感呗.jpg

我们考虑按顺序(从前向后,不留空位)给奶牛排位置,然后影响转移的就只有最后一只奶牛了。

于是设 \(dp_{s,i}\) 为选择状态为 \(s\) 且最后一只奶牛编号为 \(i\) 时的方案数。

所以有:

\[dp_{s,i}=\sum_{j\not =i\& j\not\in s}\left(dp_{s \oplus \left(1 << (i-1)\right),j}\times[|S_i-S_j>K|]\right) \]

时间复杂度是 \(\mathcal O(2^nn^2)\)

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

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

ll dp[1<<MAXN][MAXN];
int n,val[MAXN],k;
int vis[MAXN];
ll ans=0;

int main()
{
	read(n),read(k);
	for(int i=1;i<=n;i++) read(val[i]);
	for(int i=1;i<=n;i++) dp[1<<(i-1)][i]=1ll;
	for(int s=1;s<(1<<n);s++)
	{
		memset(vis,0,sizeof(vis));
		for(int j=0;j<n;j++) if((1<<j)&s) vis[j+1]=1;
		for(int j=1;j<=n;j++)
		{
			if(!vis[j]) continue;
			if(!dp[s][j]) continue;
			for(int i=1;i<=n;i++)
			{
				if(vis[i]) continue;
				if(abs(val[i]-val[j])<=k) continue;
				dp[s^(1<<(i-1))][i]+=dp[s][j];
			}
		}
	}
	for(int i=1;i<=n;i++) ans+=dp[(1<<n)-1][i];
	printf("%lld\n",ans);
	return 0;
}

\(89\) P3952 时间复杂度

由于集训模拟赛两道大细节题(字符串大膜你+复杂高精)把我送退役,故来刷 NOIP 有关模拟的题。

不得不说比集训的那俩良心多了。

直接模拟即可,我的复杂度是 \(\mathcal O(TL)\)

论时间复杂度的时间复杂度/cy/cy

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

#define mem(s) memset(s,0,sizeof(s))

int t,n,w;
int st[105],top=0;;
char s[10];
int er=0;
int vis[30];
int bo[105];

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		mem(st),mem(vis),mem(bo);
		er=0,top=0;
		scanf("%d",&n);
		scanf("%s",s);
		if(s[2]=='1') w=0;
		else
		{
			if(s[5]>='0'&&s[5]<='9') w=(s[4]-'0')*10+s[5]-'0';
			else w=s[4]-'0';
		}
		int now=0,ty=0,flg=0;
		for(int k=1;k<=n;k++)
		{
			scanf("%s",s);
			if(s[0]=='F') 
			{
				top++;
				scanf("%s",s);
				if(vis[s[0]-'a'+1]==1) er=1;
				st[top]=s[0]-'a'+1;
				vis[st[top]]=1;
				scanf("%s",s);
				if(s[0]>='0'&&s[0]<='9')
				{
					int fir,sec;
					if(strlen(s)==2) fir=(s[0]-'0')*10+s[1]-'0';
					else fir=s[0]-'0';
					scanf("%s",s);
					if(s[0]=='n')
					{
						if(!flg) ty++,bo[top]=1;	
					}
					else 
					{
						if(strlen(s)==2) sec=(s[0]-'0')*10+s[1]-'0';
						else sec=s[0]-'0';
						if(sec<fir) flg=1,bo[top]=2;
					}
				}
				else 
				{
					scanf("%s",s);
					if(s[0]!='n') flg=1,bo[top]=2;
				} 
				now=max(now,ty);
			}
			else if(s[0]=='E')
			{
				if(top)
				{
					vis[st[top]]=0;
					if(bo[top]==1) ty--,bo[top]=0;
					else if(bo[top]==2) flg=0,bo[top]=0;
					top--;
				}
				else er=1;
			}
		}
		if(top) er=1;
		if(er) puts("ERR");
		else if(now!=w) puts("No");
		else puts("Yes");
	}
	return 0;
}

\(90\) P3403 跳楼机

同余最短路。

考虑在 \(\bmod x\) 意义下,到达 \(j\) 至少要到的高度。

可以用最短路实现优化,最后记录答案即可。

注意起点是 \(1\) 而不是 \(0\)

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

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

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

priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
ll dis[MAXN];
int vis[MAXN];
int x,y,z;
ll h,ans=0;
struct node
{
	int to,nxt;
	ll w;
}e[MAXN<<1];
int head[MAXN],cnt=0;

void add(int u,int v,ll w){e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u],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]+e[i].w) dis[j]=dis[u]+e[i].w,q.push(make_pair(dis[j],j));
		}
	}
	return;
}

int main()
{
	scanf("%lld",&h);
	read(x),read(y),read(z);
	if(x==1||y==1||z==1) return printf("%lld\n",h),0;
	for(int i=0;i<=x-1;i++) add(i,(i+z)%x,(ll)z),add(i,(i+y)%x,(ll)y);
	dis[1]=1ll;
	q.push(make_pair(1ll,1));
	for(int i=0;i<=x-1;i++) if(i!=1) dis[i]=(ll)((1ull<<63)-1);
	dij();
	for(int i=0;i<=x-1;i++) if(dis[i]<=h) ans+=(h-dis[i])/(ll)x+1;
	printf("%lld\n",ans);
	return 0;
}

\(91.\) P4141 消失之物

特别好的一道 dp,我们考虑把背包优化一下。

当我们枚举不选哪个物品时,总方案数显然不能从 \(j-w_i\) 这个位置转移来,减去即可(原谅我 \(w,v\) 用混了吧 qwq)

最后注意减去的这部分转移也要保证不能选这个数。

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

(ps:这题我其实想到了 FFT 的无脑做法,有科技就是好啊然而我忘了怎么写了

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

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

int dp[MAXN][2];
int n,m,w[MAXN];

int main()
{
	read(n),read(m);
	for(int i=1;i<=n;i++) read(w[i]);
	dp[0][0]=dp[0][1]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=w[i];j--)
		{
			dp[j][0]=(dp[j][0]+dp[j-w[i]][0])%10;
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(j>=w[i]) dp[j][1]=(dp[j][0]-dp[j-w[i]][1]+10)%10;//注意减去的也要保证不拿这个的条件 
			else dp[j][1]=dp[j][0];
			printf("%d",dp[j][1]);
		}
		puts("");
	}
	return 0;
}

\(92.\) P3431 [POI2005]AUT-The Bus

啊,一眼离散化。

这玩意是 dp 啊,区间最大值?

考虑线段树(原谅我不会树状数组吧......)

然后对于每一行覆盖过去就完事了,时间复杂度是是 \(\mathcal O(k\log k)\)

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

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

int n,m,k;
int xx[MAXN],yy[MAXN];
struct node
{
	int val,id;
}aa[MAXN];
struct P
{
	int x,y,p;
}op[MAXN];
int to[MAXN];
struct Tree
{
	int l,r,rec;
}a[MAXN<<2];
int cnt=0,lst=0,ans=0;

int logx(int x){return floor(log(x)/log(2));}

bool cmp(node n,node m){return n.val<m.val;}

bool cmpp(P n,P m){if(n.y==m.y) return n.x<m.x;else return n.y<m.y;}

void hash()
{
	sort(aa+1,aa+k+1,cmp),cnt=0,lst=0;;
	for(int i=1;i<=k;i++)
	{
		if(aa[i].val!=lst) cnt++,lst=aa[i].val;
		to[aa[i].id]=cnt;	
	}
	return;
}

void update(int k){a[k].rec=max(a[k<<1].rec,a[k<<1|1].rec);}

void build(int k,int l,int r)
{
	a[k].l=l,a[k].r=r;
	if(l==r){a[k].rec=0;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].rec=y;return;}
	(x<=a[k<<1].r)?turn(k<<1,x,y):turn(k<<1|1,x,y);
	update(k);
}

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

int main()
{
	read(n),read(m),read(k);
	for(int i=1;i<=k;i++) read(xx[i]),read(yy[i]),read(op[i].p);
	for(int i=1;i<=k;i++) aa[i].val=xx[i],aa[i].id=i;
	hash();
	int h=cnt;
	for(int i=1;i<=k;i++) op[i].x=to[i];
	for(int i=1;i<=k;i++) aa[i].val=yy[i],aa[i].id=i;
	hash();
	for(int i=1;i<=k;i++) op[i].y=to[i];
	sort(op+1,op+k+1,cmpp);
	build(1,1,h);
	for(int i=1;i<=k;i++)
	{
		int now=query(1,1,op[i].x);
		ans=max(ans,now+op[i].p);
		turn(1,op[i].x,now+op[i].p);
	}
	printf("%d\n",ans);
	return 0;	
}

\(93.\) CF767E Change-free

可以反悔的贪心。

考虑能选就选。

如果把从前选过的一天的方案与今天互换更优的话就换掉,可以用堆来维护。

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


\(94.\) P2949 [USACO09OPEN]Work Scheduling G

可可反悔贪心经典题例。

能选就选(这是建立在按结束日期排序的基础上的),如果不能选把前边比他劣的替换掉即可。

同样是用堆来优化,可以做到 \(\mathcal O(n\log n)\)


\(95.\) P2431 正妹吃月饼

考虑二进制。

贪心的把最小的数二进制为 \(0\) 的位置改成 \(1\),然后和最大值比大小即可。

这时候我们要从最小的位置开始贪心。

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


\(96.\) P3052 [USACO12MAR]Cows in a Skyscraper G

正解是状压(这个数据一看就是啊。。。

但是我是搜贪心标签做的,所以就贪心的每次选择使得剩下的体积最小即可。

所以 dfs 就好了,时间复杂度是 \(\mathcal O(2^nn^2)\)???

算了我也不知道

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

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

int n,W,c[MAXN];
int vis[MAXN];
int cnt=0,minx;
int ma[MAXN],sta[MAXN];
int r;

bool cmp(int n,int m){return n>m;}

void dfs(int l,int x)
{
	if(x<c[r]||l==r)
	{
		if(x<minx)
		{
			minx=x;
			for(int i=1;i<=n;i++) sta[i]=ma[i];
		}
		else if(x==minx)
		{
			int c1=0,c2=0;
			for(int i=1;i<=n;i++)
			{
				if(sta[i]) c1++;
				if(ma[i]) c2++;
			}
			if(c1<c2)
			{
				for(int i=1;i<=n;i++) sta[i]=ma[i];
			}
		}
		return;
	}
	for(int i=l+1;i<=n;i++)
	{
		if(vis[i]) continue;
		if(x<c[i]) continue;
		ma[i]=1;
		dfs(i,x-c[i]);
		ma[i]=0;
	}
}

int main()
{
	read(n),read(W);
	for(int i=1;i<=n;i++) read(c[i]);
	sort(c+1,c+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		if(vis[i]) continue;
		minx=0x7fffffff,vis[i]=1;
		memset(ma,0,sizeof(ma));
		memset(sta,0,sizeof(sta));
		for(int j=n;j>=1;j--) if(!vis[j]){r=j;break;}
		if(!r){cnt++;break;}
		dfs(i,W-c[i]);
		for(int j=i+1;j<=n;j++) if(sta[j]) vis[j]=1;
		cnt++;
	}
	printf("%d\n",cnt);
}

\(97.\) P2295 MICE

这是一道一看就是 dp 的 dp。

朴素的直接转移是不行的,因为我们统计的是种类。

虽然走法相异,但其实只有两种情况对下面的统计造成影响,就是上一步的上一步是哪个方向的。

所以设 \(dp_{i,j,0/1}\) 为在 \((i,j)\) 这个位置,上一部是向下、向左的最小答案,依题意更新即可,时间复杂度是 \(\mathcal O(n^2)\)

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

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

int n,m;
int dp[MAXN][MAXN][2],a[MAXN][MAXN]={0};
//0上1左

int main()
{
	read(n),read(m);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++) read(a[i][j]),dp[i][j][0]=dp[i][j][1]=0x7fffffff;
	}
	dp[2][1][0]=a[1][1]+a[2][1]+a[3][1]+a[1][2]+a[2][2];
	dp[1][2][1]=a[1][1]+a[1][2]+a[1][3]+a[2][1]+a[2][2];
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(i==n&&j==m) continue;
			if(i<n)
			{
				int x,y;
				if(i>1) x=dp[i][j][0]+a[i+1][j-1]+a[i+1][j+1]+a[i+2][j];
				else x=0x7fffffff;
				if(j>1) y=dp[i][j][1]+a[i+1][j+1]+a[i+2][j];
				else y=0x7fffffff;
				dp[i+1][j][0]=min(x,min(dp[i+1][j][0],y));
			}
			if(j<m)
			{
				int x,y;
				if(i>1) x=dp[i][j][0]+a[i+1][j+1]+a[i][j+2];
				else x=0x7fffffff;
				if(j>1) y=dp[i][j][1]+a[i-1][j+1]+a[i+1][j+1]+a[i][j+2];
				else y=0x7fffffff;
				dp[i][j+1][1]=min(x,min(dp[i][j+1][1],y));
			}
		}
	}
	return printf("%d\n",min(dp[n][m][0],dp[n][m][1])),0;
} 

\(98.\) SP2713 GSS4 - Can you answer these queries IV

这是一类套路,今天才实现。

考虑暴力修改,线段树辅助优化复杂度套上求和。

时间复杂度是是 \(\mathcal O(\text{修改次数}\times n+n\log n)\)

#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()
{
    while(scanf("%d",&n)!=EOF)
    {
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++) scanf("%lld",&t[i]);
        build(1,1,n);
        read(m);
        printf("Case #%d:\n",++cnt);
        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;
}

\(99.\) P2713 罗马游戏

可并堆模板题,这里采用配对堆实现,代码魔改自模板题。

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

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

int n,m;
int f[MAXN],val[MAXN];
char t[3];
int x,y;
struct node
{
    int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int le[MAXN];

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

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

int merge(int u,int v)
{
    int t1=getf(u),t2=getf(v);
    if(t1==t2) return 0;
    if(val[t1]>val[t2]||(val[t1]==val[t2]&&t1>t2)) swap(t1,t2);
    f[t2]=t1,add(t1,t2);
    return t1;
}

void del(int u)
{
    int lst=0;
    for(int i=head[u];i;i=e[i].nxt)
    {
        int j=e[i].to;
        f[j]=j;
        if(!lst) lst=j;
        else lst=merge(lst,j);
    }
    f[u]=lst;
    return;
}

int main()
{
    read(n);
    for(int i=1;i<=n;i++) read(val[i]),f[i]=i,le[i]=1;
    read(m);
	for(int i=1;i<=m;i++)
    {
        scanf("%s",t),read(x);
        if(t[0]=='M')
        {
            read(y);
            if(le[x]&&le[y]) merge(x,y);
        }
        else
        {
            int op=getf(x);
            if(!le[x]) printf("0\n");
            else
            {
            	printf("%d\n",val[op]);
            	le[op]=0;
            	del(op);
	     }
        }
    }
    return 0;
}

\(100.\) P4028 New Product

BSGS 板子题。

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

#define ll long long 
#define MAXN 200005
#define read(x) scanf("%lld",&x)

int t;
ll p,a,b;
int h;
struct num
{
    ll val;
    int id;
}op[MAXN];

bool cmp(num n,num m){if(n.val==m.val) return n.id>m.id;else return n.val<m.val;}

void get()
{
    ll now=b;
    for(int i=0;i<h;i++)
    {
        op[i+1].id=i,op[i+1].val=now;
        now=now*a%p;
    }
    sort(op+1,op+h+1,cmp);
    return;
}

int find()
{
    ll now,mi=1;
    for(int i=1;i<=h;i++) mi=mi*a%p;
    now=mi;
    for(int i=1;i<=h;i++)
    {
        int l=1,r=h,mid;
        while(l<r)
        {
            mid=(l+r)>>1;
            if(op[mid].val<now) l=mid+1;
            else r=mid;
        }
        if(op[l].val==now) return i*h-op[l].id;
        now=now*mi%p;
    }
    return -1;
}

void work()
{
    get();
    int rt=find();
    if(rt<0) puts("Couldn't Produce!");
    else printf("%d\n",rt);
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        read(p),read(a),read(b);
        a%=p;
        if(b==1){puts("0");continue;}
        if(b>=p||a==0){puts("Couldn't Produce!");continue;}
        h=ceil(sqrt(p));//注意是上取整
        work();
        memset(op,0,sizeof(op));
    }
    return 0;
}

下一章精彩继续:口胡(然而有代码)<第三章>~~

posted @ 2020-10-06 22:46  童话镇里的星河  阅读(113)  评论(0编辑  收藏  举报