这里主要收录的是一些算法并不难然而自己没能很快AC的题目,也算是整理一下自己的粗心错误。从2017.12.12开始更新

题楼

这里主要收录的是一些算法并不难然而自己没能很快AC的题目,也算是整理一下自己的粗心错误。从2017.12.12开始更新

luogu1186 玛丽卡

这道题很容易看出来是spfa,先找出最短路,然后一条一条堵住找最短路。
一开始以为要求的是找出的这些最短路里面最小的值,但其实不是这样的。题目要求的其实可以概括为“给出一个时间,堵住特定的一条路,使得堵住后在这个时间内玛丽卡无法到达终点”,仔细思考一下,应该是最大值——假设使最短路达到最大值要堵住的这条边被堵死了,那么在这个最大的最短路之内的时间内是不可能到达终点的,而这个时间内也是包括其它那些堵住其它边的得到的较小的最短路。
这样只需要在spfa的过程中不走特定的某条边就行了。但是我这里疏忽了一件事,这也是让我把这道题添加到题楼里面的原因:我在记录最短路的每条边的编号的时候,用的是出发点来标记,但这样是不对的,因为有可能有多条最短路,而这个出发点也有可能在不同的最短路里面有不同的出边,所以不能标记出发点,但是标记到达点的话就不会有影响。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define rint register int
#define inv inline void
#define big 1e9
using namespace std;
struct node
{
	int next,to,dis,cnt;
}ljb[1000001];
int n,m,cnt,dis[1001],head[1000001],pre[1001],per[1001],num[1001],nmu[1001],hh;
bool vis[1001];
queue<int> q;
inv add(int x,int y,int z)
{
	ljb[++cnt].next=head[x];
	ljb[cnt].to=y;
	ljb[cnt].dis=z;
	ljb[cnt].cnt=cnt;
	head[x]=cnt;
	ljb[++cnt].next=head[y];
	ljb[cnt].to=x;
	ljb[cnt].dis=z;
	ljb[cnt].cnt=cnt-1;
	head[y]=cnt;
}
inv spfa(int cant)
{
	for (rint i=1;i<=n;i++) dis[i]=big;
	memset(vis,0,sizeof(vis));
	dis[1]=0;vis[1]=1;q.push(1);pre[1]=1;
	while (!q.empty())
	{
		int x=q.front();q.pop();vis[x]=0;
		for (rint i=head[x];i;i=ljb[i].next)
		{
			int y=ljb[i].to;
			if (dis[y]>dis[x]+ljb[i].dis && ljb[i].cnt!=cant)
			{
				dis[y]=dis[x]+ljb[i].dis;
				pre[y]=x;
				num[y]=ljb[i].cnt;
				if (!vis[y])
				{
					q.push(y);
					vis[y]=1;
				}
			}
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (rint i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
	}
	spfa(0);
	int x=n;
	for (rint i=1;i<=n;i++) per[i]=pre[i];
	for (rint i=1;i<=n;i++) nmu[i]=num[i];
	int ans=dis[n];
	while (x!=per[x])
	{
		spfa(nmu[x]);x=per[x];
		if (dis[n]<big) ans=max(ans,dis[n]);
	}
	printf("%d",ans);
}

luogu 1078 文化之旅

S1
这道题第一眼是最短路,看到数据范围之后感觉是弗洛伊德。但是并不能用最短路做……因为这道题的后效性过于强烈,如果用弗洛伊德,即使两个部分都不冲突,两个部分也有可能互相冲突,SPFA也不行,因为dis是根据之前的dis来更新的,而最短路不一定是最优选择,所以这道题应该没法这么做。
S2
那么就只能用搜索了。这道题由于有不同边权,所以dfs和bfs的效率都差不多,就题论题dfs更舒坦一些。但是要加上记忆化搜索。
记搜怎么做呢,当然是用dis数组来保存,如果sum大于dis那么就退出就行了。
再让我们考虑优化的问题。肯定有的数据是输出-1但是数据贼大会把你卡飞的那种。我们可以在搜索之前先判断能不能到达终点。但是这样难道还要来一遍搜索?
S3
先不考虑这个问题,搜一波试试看。没有超时看来数据比较水,但是竟然wa了四个点?? 把数组调大了一点,然后把剪枝改成了大于等于,毫无卵用。
错的四个点都是输出了-1,看来是没有搜到t点,被剪掉的可能性微乎其微,看来搜索过程中出了问题。
最后检查出来了,是cnt没有清零……不知啥时候不小心删了……
代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define rint register int
#define inv inline void
#define big 1e9
using namespace std;
int n,k,m,s,t,c[101],a[101][101],dis[101],head[200001],ans[101],cnt;
struct node
{
    int next,to,dis;
}ljb[200001];
inv add(int x,int y,int z)
{
    ljb[++cnt].next=head[x];
    ljb[cnt].to=y;
    ljb[cnt].dis=z;
    head[x]=cnt;
}
inv dfs(int x,int sum)
{
    if (dis[x]<=sum) return;
    dis[x]=sum;
    for (rint i=head[x];i;i=ljb[i].next)
    {
        int y=ljb[i].to;bool flag=0;
        for (rint j=1;j<=cnt;j++)
            if (a[c[y]][c[ans[j]]])
            {
                flag=1;
                break;
            }
        if (!flag)
        {
            ans[++cnt]=y;
            dfs(y,sum+ljb[i].dis);
            ans[cnt--]=0;
        }
    }
}
int main()
{
    scanf("%d%d%d%d%d",&n,&k,&m,&s,&t);
    for (rint i=1;i<=n;i++) scanf("%d",&c[i]);
    for (rint i=1;i<=k;i++)
    {
        for (rint j=1;j<=k;j++) scanf("%d",&a[i][j]);
        a[i][i]=1;
    }
    for (rint i=1;i<=m;i++)
    {
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,z);
    }
    for (rint i=1;i<=n;i++) dis[i]=big;cnt=0;
    ans[++cnt]=s;
    dfs(s,0);
    if (dis[t]>=big) printf("-1");
    else printf("%d",dis[t]);
}

总结:

  1. 看着像某种算法,不一定真的是某种算法
  2. 玄学错误的时候在程序中间输出一下变量,说不定有意外收获。

luogu 1436 棋盘分割

S1
无聊的我决定跟着ASIA大佬做一道题。
这题第一眼其实是贪心……我八成是傻了,本来想的是把这玩意比作分苹果,分成两份,两份比较平均的话,它们的平方和肯定要小,这个倒是可以证明,但是如果分的份数超过两份,这样的贪心就不成立了,因为如果n=3,那么肯定是把总体分成三份比较平均的要好。
S2
然后就想到记忆化搜索,用f[i][j][k][l][dep]来记录,过程不多说,比较顺,但就是用前缀和来推s[i][j][k][l]的过程中出了一点小岔子,要记住如果在前缀和上操作,要留住第i行,减去的一定是第i-1行。
S3
这道题还有asia的DP做法,思想都是一样的,但是要五重循环(第五重分别枚举行列),时间稍慢一点。
代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define rint register int
#define inv inline void
#define big 1e9;
using namespace std;
int n,a[11][11],s[11][11][11][11],f[11][11][11][11][16];
inv dfs(int a,int b,int c,int d,int dep)
{
    if (f[a][b][c][d][dep]) return;
    int ans=big;
    for (rint i=a;i<=c-1;i++)
    {
        dfs(a,b,i,d,dep+1);
        dfs(i+1,b,c,d,dep+1); 
        ans=min(ans,min(s[a][b][i][d]+f[i+1][b][c][d][dep+1],f[a][b][i][d][dep+1]+s[i+1][b][c][d]));
    }
    for (rint i=b+1;i<=d;i++)
    {
        dfs(a,i,c,d,dep+1);
        dfs(a,b,c,i-1,dep+1);
        ans=min(ans,min(s[a][i][c][d]+f[a][b][c][i-1][dep+1],f[a][i][c][d][dep+1]+s[a][b][c][i-1]));
    }
    f[a][b][c][d][dep]=ans;	
}
int main()
{
    scanf("%d",&n);
    for (rint i=1;i<=8;i++)
        for (rint j=1;j<=8;j++)
        {
            scanf("%d",&a[i][j]);
            a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+a[i][j];
        }
    for (rint i=1;i<=8;i++)
        for (rint j=1;j<=8;j++)
            for (rint k=1;k<=8;k++)
                for (rint l=1;l<=8;l++)
                {
                    s[i][j][k][l]=a[k][l]-a[k][j-1]-a[i-1][l]+a[i-1][j-1];
                    s[i][j][k][l]=s[i][j][k][l]*s[i][j][k][l];
                    f[i][j][k][l][n]=s[i][j][k][l];
                }
    dfs(1,1,8,8,1);
    printf("%d",f[1][1][8][8][1]); 
}

总结:

  1. 大胆尝试DFS的可能
  2. 贪心做法除非万不得已,否则最好证明了再打。

luogu 2672 推销员

S1
已经连续被12、14、17三年普及组的最后一题shut down了,希望这道别崩。
算法妥妥的贪心。最初确立了一个策略,就是先算出一个路程加上价值最大的,也就是只推销一家的话应该是哪一家。我们可以保证,肯定没有比他远还比他贵的。所以如果不选这个点,而去选比它远的,肯定不如选这个要划算。那么我们可以这样做,比它近的,权值就是本来的权值,比它远的,权值就是多出来的路程+本来的权值。排一下序,然后一个一个往上面加就行了。
S2
WTF??过了??
终于没被PJ T4给shut down,心里还是比较欣慰的
代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rint register int
#define inb inline bool
using namespace std;
int n,s[100001],a[100001],f[100001];
inb cmp(int x,int y)
{
    return x>y;
}
int main()
{
    scanf("%d",&n);
    for (rint i=1;i<=n;i++) scanf("%d",&s[i]);
    for (rint i=1;i<=n;i++) scanf("%d",&a[i]); 
    int m,maxx=0,cnt=0;
    for (rint i=1;i<=n;i++)
        if (2*s[i]+a[i]>maxx) 
        {
            maxx=2*s[i]+a[i];
            m=i;
        }
    for (rint i=1;i<=m-1;i++) f[++cnt]=a[i];
    for (rint i=m+1;i<=n;i++) f[++cnt]=2*(s[i]-s[m])+a[i];
    sort(f+1,f+cnt+1,cmp);
    printf("%d\n",maxx);
    for (rint i=1;i<=cnt;i++) 
    {
        maxx+=f[i];
        printf("%d\n",maxx);
    } 
}

总结:

  1. 当思路不清晰的时候,把它打出来,就会越来越清晰
  2. 开拓想象力,但也要理性贪心
  3. 排序要想好从小往大还是从大往小,不对的时候检查一下这里。

luogu 1330 封锁阳光大学

S1
这个题好迷啊。本来是打算练图论才进来的,没想到这是个搜索,或者说,图的遍历。
思考了一会,有一个初版的思路:因为都是无向图,所以可以互相通达,所以我可以随便找一个点,那么它的下一个点的下一个点,它的下一个点的下一个点的下一个点,也就是1,3,5,都可以走到,然后我再搜一下2,4,6,两种情况取最小值,如果两种都不行那么输出impossible。
S2
额,第一遍好像只有五十分,算法似乎没问题,但是错的几个点都输出了impossible。
一开始以为是清空vis数组的位置错了,不过改了好像没什么卵用。
发现这个题广搜似乎更加方便,自己莫不是傻了。
还有一点就是只要搜一遍就行了,如果行的通的话直接比较ans和(n-ans)就可以
S3
有突然想到一种思路
两者互相冲突的情况似乎不可能发生在无环图里面,那么有环图中的偶数边的环似乎也可存在,那么我可以先写一个dfs来判断有没有奇数环,然后如果没有就可以跑一个bfs来计算就可以了。
中途发现了一个问题:这个题并不是一个连通图,所以我们要进行多次广搜,还要把孤点排掉。一开始排孤点直接在n上减,但这样会导致后面的bfs操作错误,而且我们不能这样直接排,还是那句话,这个图不是联通的,所以每个不联通的小图都可以有不同的选法,我们要选每个小图里面较小的选法,把它们加起来,才能得出正确的结果(这个点卡了半天的60分)。
最后,初始化广搜队列的memset必须去掉,不然就得卡掉一个点……
代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define rint register int
#define inv inline void
#define big 1e9
using namespace std;
int ans,sum,tot,answer,cnt,n,m,num[200001],head[200001],team[1000001][3],use[10001];
bool vis[10001],flag;
struct node
{
    int next,to;
}ljb[200001];
inv add(int x,int y)
{
    ljb[++cnt].next=head[x];
    ljb[cnt].to=y;
    head[x]=cnt;
}
inv dfs(int x,int dep)
{
    vis[x]=1;use[x]=dep;
    for (rint i=head[x];i;i=ljb[i].next)
    {
        int y=ljb[i].to;
        if (use[y]) 
        {
            if ((use[x]+1-use[y])%2!=0)	
            {
                flag=1;
                return;
            }
        }  
        if (!vis[y]) dfs(y,dep+1);
    }
}
inv bfs(int x)
{
    team[1][1]=x;team[1][2]=1;vis[x]=1;
    int hed=0,tail=1;
    ans=0;sum=0;
    while (hed<tail)
    {
        int now=team[++hed][1];
        int bj=team[hed][2];
        if (bj) ans++;sum++;
        for (rint i=head[now];i;i=ljb[i].next)
        {
            int y=ljb[i].to;
            if (!vis[y])
            {
                vis[y]=1;
                team[++tail][1]=y;
                team[tail][2]=bj^1;
            }
        }
    }
    answer+=min(ans,sum-ans);
}
int main()
{
    scanf("%d%d",&n,&m);
    int maxx=0,t1,t2;
    for (rint i=1;i<=m;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
        num[y]++;num[x]++;
    }
    int tot=n;
    for (rint i=1;i<=n;i++)
    {
        if (!vis[i])
        {
            memset(use,0,sizeof(use));
            flag=0;
            dfs(i,1);
            if (flag) 
            {
                printf("Impossible");
                exit(0);
            }
        }
        if (!num[i]) tot--;
    }
    memset(vis,0,sizeof(vis));
    for (rint i=1;i<=n;i++)
        if (!vis[i] && num[i])
            bfs(i);
    printf("%d",answer);
}

总结:

  1. 搜索之前先想好是dfs还是bfs
  2. 大胆尝试多种搜索方法结合的可能性
  3. 最重要的一点,我在做这个题的时候一开始以为用总的点减去每个小图的选择,再和每个小图的选择之和相比较就能出结果,这可以说是数学思维的欠缺……因为每个小图有可能选的是大的也有可能选的是小的方案,所以不能这么选。遇到这种分成子问题的情况一定要好好想一想。

luogu 2744 量取牛奶

S1
一开始看到这道题还是很好确定算法的,妥妥的是dfs+dp。先跑一遍完全背包,vis[i][j]表示体积为j的时候选不选i最优,可以把第一维压掉。如果vis[j-a[i]]选了i,那么vis[j]就不用再选一遍。这样可以确定出来一共选了多少种木桶,之后搜索,再跑布尔完全背包,因为已经把桶的大小排过序了,那么如果可以拼成体积为q的就直接输出结束程序就可以了。
S2
出了两个小错误。 第一个,从五十分改到六十分。在最初的那一遍完全背包中,如果f[j-a[i]]+value等于f[j],那也要把vis[j]设为1,因为这样会最优——在同样的f[j]下,如果vis[j]=1,那么f[j+a[i]]的值会少加1,从而保证最终的结果最优。
第二个,从六十分改到一百分。这个真是无语,小错误害死人啊。在搜索完的那次完全背包中,我的初始化不是g[i]=big,而是g[q]=big……手一滑,三个人检查了一个小时,死活检查不出来,从网上下了数据才出来的……检查的时候一定要一个字一个字仔细看,不要因为语句简单就不看了,简单的语句也可能手滑打错。
代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define rint register int
#define inv inline void
#define big 1e9
using namespace std;
int p,q,a[101],f[20001],que[101],cnt;
bool use[101],vis[20001],g[20001];
inv dp()
{
	for (rint i=0;i<=q;i++) g[i]=0;g[0]=1;
	for (rint i=1;i<=f[q];i++)
		for (rint j=a[que[i]];j<=q;j++)
			if (g[j-a[que[i]]]) g[j]=1;
	if (g[q])
	{
		printf("%d ",f[q]);
		for (rint i=1;i<=f[q];i++) printf("%d ",a[que[i]]);
		exit(0);
	}
}
inv dfs(int x,int dep)
{
	if (dep==f[q]) 
	{
		dp();
		return;
	}
	if (f[q]-dep>p-x) return;
	for (rint i=x+1;i<=p;i++)
	{
		que[dep+1]=i;
		dfs(i,dep+1);
	}
}
int main()
{
	scanf("%d%d",&q,&p);
	for (rint i=1;i<=p;i++) scanf("%d",&a[i]);
	sort(a+1,a+p+1);
	for (rint i=0;i<=q;i++) f[i]=big;f[0]=0;	
	for (rint i=1;i<=p;i++)
		for (rint j=0;j<=q;j++)
		{
			vis[j]=0;
			if (j>=a[i])
			{
				int value=vis[j-a[i]]^1;
				if (f[j-a[i]]+value<=f[j])
				{
					f[j]=f[j-a[i]]+value;
					vis[j]=1;
				}
			}
		}
	dfs(0,0);
} 

总结:

  1. 要思考一下第一个搜出来的是不是最优解,是的话可以直接剪掉。
  2. 一定要注意细节,等于号加不加是个问题,仔细思考。出错的时候一句一句认真检查,千万不要嫌麻烦,发愁的时间比检查的时间长多了。

luogu 3045 牛券

S1
这道题第一眼是DP或者贪心,贪心的几率显然更大。对于这种题,显然的有两种贪心方案,第一种,差值最大的贡献最大,第二种,减价后最小的贡献最大。
S2
在这里我犯了一个错误,就是一个劲去证明第一种方案的不可行性,然后不管第二个方案……其实应该把不可行性写出来,然后发现不可以直接去想第二个。正在我瓶颈的时候zyc告诉我这题要联系生活实际,一下子茅塞顿开了。在生活中,我们肯定会先选用优惠劵之后价格最低的k个,然后再卖剩下的里面实际价格最小的,最后凑到m就行了。 因为没有把m开成long long 错了两个点,一定要细心啊。。。
代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define rint register int
using namespace std;
int n,k,p[50001],c[50001];
long long m;
bool vis[50001];
struct node
{
	int p,c,id;
}a[50001];
bool cmp1(node a,node b)
{
	return a.c<b.c;
}
bool cmp2(node a,node b)
{
	return a.p<b.p;
}
int main()
{
	scanf("%d%d%lld",&n,&k,&m);
	for (rint i=1;i<=n;i++)
	{
		scanf("%d%d",&a[i].p,&a[i].c);
		a[i].id=i;
	}
	sort(a+1,a+n+1,cmp1);
	long long tot=0;
	int num=0;
	for (rint i=1;i<=k;i++)
	{
		if (tot+a[i].c>m)
		{
			printf("%d",i-1);
			exit(0);
		}
		tot+=a[i].c; 
		vis[a[i].id]=1;
	}
	sort(a+1,a+n+1,cmp2);
	for (rint i=1;i<=n;i++)
		if (!vis[a[i].id])
		{
			if (tot+a[i].p>m)
			{
				printf("%d",k+num);
				exit(0);
			}
			num++;
			tot+=a[i].p;
		}
	printf("%d",n);
}

总结

  1. 有多种方案的时候证明不可行性,要把它写出来,把各个方案都想一下。
  2. 这种买东西的贪心题,如果题目不是太扯淡,可以联系一下生活实际,也就是在生活中我该怎么做,说不定能有意外的收获。
  3. 开long long ,开 longlong ,开long long 。

    后记

    这种做法被这组数据hack掉了
    8 3 10
    2 1
    2 1
    2 1
    10000000 2
    10000000 2
    10000000 2
    10000000 2
    10000000 2
    只能说是数据太水。实际上这题需要三个优先队列来进行骚操作才能AC……等有能力做的时候再补上正解吧。

luogu 1983 车站分级

S1
一开始看这个题题意还是没有看懂的,主要是没看见每一条路都有自己的起点和终点站……一定要注意认真读题。然后想算法,这个题题意就是一个拓扑排序,数据也小,可以浪。
S2
中间出了几个小细节的问题:
第一个,还是忘了每条线路都有起点和终点站,预处理的时候从1到n循环的,出了大错。
第二个,把tot加了两遍……
第三个,每一遍都把sum[i]=0的点入队,而没有把访问过的标记掉。
去掉这三个小错误就从十分变成了一百分,所以一定要注意细节!
代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define rint register int
using namespace std;
int n,m,map[1001][1001],sum[1001],a[1001],que[1001],s[1001];
bool vis[1001];
int main()
{
	scanf("%d%d",&n,&m);
	for (rint q=1;q<=m;q++)
	{
		scanf("%d",&s[0]);
		for (rint i=1;i<=n;i++) vis[i]=0;
		for (rint i=1;i<=s[0];i++)
		{
			scanf("%d",&s[i]);
			vis[s[i]]=1;
		}
		for (rint i=s[1];i<=s[s[0]];i++)
			if (!vis[i])
				for (rint j=1;j<=s[0];j++)
					if (!map[i][s[j]])
					{
						map[i][s[j]]=1;
						sum[s[j]]++;
					}
	}
	int ans=0,num=0;
	for (rint i=1;i<=n;i++) vis[i]=0;
	while (num<n)
	{
	    int tot=0;
		for (rint i=1;i<=n;i++) 
			if (!sum[i] && !vis[i]) 
			{
				que[++tot]=i;
				vis[i]=1;
				num++;
			}
		for (rint i=1;i<=tot;i++)
			for (rint j=1;j<=n;j++)
				if (map[que[i]][j])
				{
					map[que[i]][j]=0;
					sum[j]--;	
				}	
		ans++;
	}
	printf("%d",ans);	
} 

总结

  1. 一定要认真读题,结合样例来读,还有就是看题图很重要。
  2. 注意细节,冷静打代码,除非万不得已不要飚手速。

TO BE CONTINUED

赞 赏
蒟蒻的邮箱:xmzl200201@126.com