NOIp2018提高组游记

Day1

T1 积木大赛

NOIp2013D2T1.....看到的时候我还以为我记错了,以为原题是一次可以随便加,这题只能加一,出考场后查了下发现一模一样。

#include <iostream>
#include <cstdio>

using namespace std;

const int N=100010;

int n,d[N],ans;

int main()
{
    int i;
    
    scanf("%d",&n);
    
    for (i=1;i<=n;++i)
    {
        scanf("%d",d+i);
        if (d[i]>d[i-1])
        {
            ans+=d[i]-d[i-1];
        }
    }
    
    cout<<ans;
    
    return 0;
}

T2 货币系统

去掉可以由其它货币拼成的货币,这个结论倒是很快猜到了。但由于xkdyh留下的阴影,一开始我还写了个exgcd...幸好大样例比较良心,有一组数据是三种货币拼成另一种。然后再仔细一看,发现是个完全背包...

简单证明一下:

结论:对于一个没有任何一种货币可以由系统内其它货币拼成的货币系统 ((n,A)),与其等价的货币系统 ((m,B)) 只能是 ((n,A)) 自身或者加上一些能由 ((n,A)) 表示的数。

(A otsubseteq B),任取 (tin (A-B)),那么在 (B) 中必然有一些元素能够拼成 (t),而这些元素在 (A) 中必然有不能表示的(否则与 (A) 中没有任何一种货币可以由系统内其它货币拼成矛盾),而存在 ((m,B)) 能表示而 ((n,A)) 不能表示的数与 ((n,A),(m,B)) 等价矛盾,不成立。

(B) 中有 ((n,A)) 所不能表示的元素,依然与 ((n,A),(m,B)) 等价矛盾,不成立。

所以命题得证。

排个序然后完全背包去掉能被系统内其它货币表示的货币即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int t,n,a[110],maxx;
bool f[25010];

int main()
{
    int i,j,ans;
    
    scanf("%d",&t);
    
    while (t--)
    {
        scanf("%d",&n);
        ans=n;
        maxx=0;
        for (i=1;i<=n;++i)
        {
            scanf("%d",a+i);
            maxx=max(maxx,a[i]);
        }
        sort(a+1,a+n+1);
        memset(f,false,sizeof(f));
        f[0]=true;
        for (i=1;i<=n;++i)
        {
            if (f[a[i]])
            {
                --ans;
                continue;
            }
            for (j=0;j+a[i]<=maxx;++j)
            {
                if (f[j])
                {
                    f[j+a[i]]=true;
                }
            }
        }
        printf("%d
",ans);
    }
    
    return 0;
}

T3 赛道修建

看到这题就想起了ylh当时跟我一个房间的时候切掉的 CF div.2 E,但赛后发现不一样...

出考场得知dew、ylh都切掉了这题,然而我只写了直径、链和菊花图的 (55) 分...凉凉凉

Day2

T1 旅行

一开始看错题了,以为是最小字典序生成树,还在想为什么 (m) 这么小..然后仔细一看题,发现一条边只能回溯时重复经过,也就是最后得到的序列只能是个dfs序...数据范围很小,所以就枚举断边写了个 (O(n^2)) 的,预处理边排序。然后出考场听一堆dalao在那说各种 (O(nlogn))(O(n)) 做法...都不会QAQ

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>

using namespace std;

const int N=5010;

void dfs(int u);

int n,m,cut,a[N][N],tot,e[N][2];
bool vis[N],used[N]; //used用于对m个字典序取min,若used[i]=true说明断开第 i 条边时一定不是答案
vector<int> g[N];

int main()
{
	int i,j,u,v,minn;
	
	scanf("%d%d",&n,&m);
	
	for (i=0;i<m;++i)
	{
		scanf("%d%d",&u,&v);
		e[i][0]=u;
		e[i][1]=v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	for (i=1;i<=n;++i)
	{
		sort(g[i].begin(),g[i].end());
	}
	
	if (n==m)
	{
		for (cut=0;cut<m;++cut)
		{
			memset(vis,false,sizeof(vis));
			tot=0;
			dfs(1);
			if (tot<n)
			{
				used[cut]=true;
			}
		}
		for (i=1;i<=n;++i)
		{
			minn=n;
			for (j=0;j<m;++j)
			{
				if (!used[j]&&a[j][i]<minn)
				{
					minn=a[j][i];
				}
			}
			for (j=0;j<m;++j)
			{
				if (a[j][i]>minn)
				{
					used[j]=true;
				}
			}
			printf("%d",minn);
			if (i<n)
			{
				putchar(' ');
			}
		}
	}
	
	else
	{
		cut=m;
		dfs(1);
		for (i=1;i<=n;++i)
		{
			printf("%d",a[m][i]);
			if (i<n)
			{
				putchar(' ');
			}
		}
	}
	
	return 0;
}

void dfs(int u)
{
	if (vis[u])
	{
		return;
	}
	vis[u]=true;
	a[cut][++tot]=u;
	int v,i;
	for (i=0;i<g[u].size();++i)
	{
		v=g[u][i];
		if ((u!=e[cut][0]||v!=e[cut][1])&&(u!=e[cut][1]||v!=e[cut][0]))
		{
			dfs(v);
		}
	}
}

T2 填数游戏

要是数据范围给到 (10^9) 我就不会在考场上推半天了...一开始想了好久怎么 (O(nm)) dp,虽然没想出来怎么做,但发现了暴力怎么写:一种方案合法等价于:对于每个点,它右边的点先往下再往右的路径小于它下面的点先往右再往下的路径。因为这两条路径分别是一个点向右走后最大的路径和向下走后最小的路径。然后打了个表,发现 ((n,m)=(n,n+1) imes 3^{m-n-1} (nge 2,mge n+1))。于是开始跑 ((8,9)) ,跑到11:50 还没跑出来...幸好发现了 ((n,n))((n,n+1)) 之间也有一定的规律,把 ((8,9)) 算出来了...

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const long long Ans[9][10]={{0,0,0,0,0,0,0,0,0,0},
					  		{0,0,0,0,0,0,0,0,0,0},
					  		{0,4,12,36,0,0,0,0,0,0},
					  		{0,8,36,112,336,0,0,0,0,0},
					  		{0,16,108,336,912,2688,0,0,0,0},
					  		{0,32,324,1008,2688,7136,21312,0,0,0},
					  		{0,64,972,3024,8064,21312,56768,170112,0,0},
					  		{0,128,2916,9072,24192,63936,170112,453504,1360128,0},
					  		{0,256,8748,27216,72576,191808,510336,1360128,3626752,10879488}}; //其实有一部分表是不必要(没有意义)的...
					   
const long long M=1000000007;

long long n,m,ans=1;

int main()
{
	int i;
	
	cin>>n>>m;
	
	if (n>m)
	{
		swap(n,m);
	}
	
	if (n==1)
	{
		for (i=30;i>=0;--i)
		{
			ans=ans*ans%M;
			if (m&(1<<i))
			{
				ans=ans*2%M;
			}
		}
		cout<<ans;
	}
	
	else
	{
		if (m<=n+1)
		{
			cout<<Ans[n][m];
			return 0;
		}
		else
		{
			for (i=30;i>=0;--i)
			{
				ans=ans*ans%M;
				if ((m-n-1)&(1<<i))
				{
					ans=ans*3%M;
				}
			}
			cout<<ans*Ans[n][n+1]%M;
		}
	}
	
	return 0;
}

T3 保卫王国

据说是ddp...考场上先10min写了44分(一开始还以为是55分Orz),然后看了下,觉得B1挺可写的,更新向上的链貌似就可以了,但最后没调出来..


Day7

上(tui)了一个星期的whk..个鬼啊,三天在考期中,就上了两天whk。感觉从零开始的whk没有想象中那么恐怖...

一周不让去机房,一到家就在洛谷上测了一下公布的代码.其它题都和预估的一样,D2T1可能会被卡常,洛谷上开了O2最慢点 (0.9s) ,而且不用vector好像也过不了,不知道是不是洛谷上内存开小了的原因...

D1T3大众AC题我爆菊(花图)了... 幸好菊花图的数据分治放在了最后面,还有 (40) 分。发现自己傻了,不知道为什么会认为只有最短的两条边可以拼在一起,其它边都只能自成一条道.......

听说D2T3不用ddp,还是我太菜了...

估分:(100+100+40+88/100+100+44=472/484)


Day10

GGF咕咕咕,然而我把两个T3写了一下..

D1T3真的好简单...二分答案,check的时候dfs处理每棵子树并返回块数最多时最大剩余,具体就是把子树返回值排个序,双指针配对得到最多块数,然后从最后一个配对的左指针开始往前这么多个依次配对,最后看剩下的没配对的里面最大的是多少。(第二天换成CCF数据发现做法挂了)处理子树的时候二分返回值不影响复杂度但能保证正确性。

D2T3做法挺有趣的..倍增题做少了,估计做多了就比较套路了...预处理出 (f[u][1],f[u][0],g[u][1],g[u][0]),分别代表选/不选 (u) 时 子树 (u) 答案,选/不选 (u)(u) 往上(整颗树减去子树 (u))的答案。倍增处理出祖先 (fa[u][i]) 表示 (u)(2^i) 祖先,用 (bz[u][i][0/1][0/1]) 表示子树 (fa[u][i]) 除去子树 (u) ,其中 (u) 选/不选,(fa[u][i]) 选/不选的答案,可以在dfs预处理 f 和 g 的同时算出 (bz[u][0][0/1][0/1]) ,然后 (bz[u][i][a][b]=min(bz[u][i-1][a][0]+bz[fa[u][i-1]][i-1][0][b],bz[u][i-1][a][1]+bz[fa[u][i-1]][i-1][1][b])) 。计算答案的时候如果是祖先关系直接倍增计算链上答案,再加上子树的 f 和 祖先上方的 g;否则倍增到 (lca) 计算路径上的答案,两棵子树以及 (lca) 上方的答案就是对应的 (f)(g) 。然后写到 (22:15),交上去 (68) 分,回寝室...ab相邻的 (16) 分真的好简单,不用倍增,考场上应该写出来的...


Day11

刚到学校听说自己 (480) ,还在想8700k这么强,能把我的 travel 卡成 (96) ...

中午一看是 (489),数据真有趣...D1T3 隔壁原 (95) 变成 (80),我昨天A的变成 (90) 了,考场写的还骗到了 (5) 分((45))...8700k天下第一!

原文地址:https://www.cnblogs.com/ouuan/p/NOIp2018.html