6.29总结

6.29总结

比赛总结

得分

估分:0+30+40=70

实际:0+100+0=100

Rank 11

神奇,T2暴力切了!

woc,T2暴力就是正解!

第三题空超爆零


T1

什么鬼,完全不会嘛。。。弃掉!

正解:

树形dp/贪心(不会)

1、将环上的任意一条边删去,就成了一棵特殊的树——也即一条链;
2、这题根本不需要考虑环的构造方案,即不需要考虑点和点之间的连接方法(因为要求的只
是最小代价)。
这使我们容易想到一种可行的构造方法:先将树转换一条链,最后再加一条连接链的首位的
边,从而转换成环。

By 《树环转换》题解——约朋

设f[i][0/1]表示把以i为根的子树变为一条链的最小代价。

\[f[i][0]=min\begin{cases} {\sum{f_{son,1}}+2*sum_{son}}\\ {\sum{f_{son,1}}-f_{son_i,1}+f_{son_i,0}+2*(sum_{son}-1)} \end{cases} \]

\[f[i][1]=min\begin{cases} {f_{i,0}}\\ {\sum{f_{son,1}}-f_{son_i,1}+f_{son_i,0}-f_{son_j,1}+f_{son_j,0}+2*(sum_{son}-2)} \end{cases} \]


T2

对于二进制串a,b,他们之间的海明距离是指两个串异或之后串中1的个数

计算两个串之间的海明距离的时候,他们的长度必须相同。现在我们给出N个不同的二进制串,请计算出这些串两两之间的最短海明距离。

一开始看错题了。以为海明距离是两个串异或之后的值。以为是一道水题,码完trie之后发现不对劲,才发现题目看错了...

比赛剩20分钟的时候,预知未来看到fjy50分决定打暴力,就在trie上面搜索,加了一个最优性剪枝,剩6分钟的时候交上去,居然切了!

出来一看题解

至于证明……只能说:如果出题人出人工数据来卡暴力的话,并不能使n=10000,ans=20,因为这些数字两两不重复,所以当ans=20,n必定为2。以此类推……极限数据不会太坑。

记住:骗分最神奇,暴力出奇迹。

By 海明距离Solution

.......牛逼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

struct qy
{
	int ch[2];
	int bz;
};

int T,i,j,n,x,tot,ans,s,k;
qy trie[100005];
int str[100005][35];
char ch[6];

void insert(int k)
{
	int x=0;
	for (int i=1;i<=20;i++)
	{
		if (trie[x].ch[str[k][i]]==0)
		{
			trie[x].ch[str[k][i]]=++tot;
			trie[tot].ch[0]=trie[tot].ch[1]=trie[tot].bz=0;
		}
		x=trie[x].ch[str[k][i]];
	}
	trie[x].bz=1;
}

void dg(int k,int x,int depth,int s)
{
	if (ans<=s) return;
	if (depth==20) ans=s;
	if (trie[x].ch[str[k][depth+1]]!=0)
	{
		dg(k,trie[x].ch[str[k][depth+1]],depth+1,s);
	}
	if (trie[x].ch[str[k][depth+1]^1]!=0)
	{
		dg(k,trie[x].ch[str[k][depth+1]^1],depth+1,s+1);
	}
}

int main()
{
	freopen("read.in","r",stdin);
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (i=1;i<=n;i++)
		{
			scanf("%s",ch+1);
			for (j=1;j<=5;j++)
			{
				if (ch[j]>='A') x=ch[j]-'A'+10;
				else x=ch[j]-'0';
				for (k=1;k<=4;k++)
				{
					if ((x&(1<<(4-k)))!=0)
					str[i][(j-1)*4+k]=1;
					else
					str[i][(j-1)*4+k]=0;
				}
			}
		}
		tot=0;
		trie[0].ch[0]=trie[0].ch[1]=trie[0].bz=0;
		ans=10000000;
		insert(1);
		for (i=2;i<=n;i++)
		{
			dg(i,0,0,0);
			insert(i);
		}
		printf("%d\n",ans);
	}
}

ps:cgh的部分代码

for i:=1 to min(500,n) do
begin
         for j:=1 to min(500,n) do
               begin
      
               end;
end;

......牛逼

什么垃圾数据啊!(摔

T3

一个关于n个元素的排列是指一个从{1, 2, …, n}到{1, 2, …, n}的一一映射的函数。这个排列p的秩是指最小的k,使得对于所有的i = 1, 2, …, n,都有p(p(…p(i)…)) = i(其中,p一共出现了k次)。

例如,对于一个三个元素的排列p(1) = 3, p(2) = 2, p(3) = 1,它的秩是2,因为p(p(1)) = 1, p(p(2)) = 2, p(p(3)) = 3。

给定一个n,我们希望从n!个排列中,找出一个拥有最大秩的排列。例如,对于n=5,它能达到最大秩为6,这个排列是p(1) = 4, p(2) = 5, p(3) = 2, p(4) = 1, p(5) = 3。

当我们有多个排列能得到这个最大的秩的时候,我们希望你求出字典序最小的那个排列。对于n个元素的排列,排列p的字典序比排列r小的意思是:存在一个整数i,使得对于所有j < i,都有p(j) = r(j),同时p(i) < r(i)。对于5来说,秩最大而且字典序最小的排列为:p(1) = 2, p(2) = 1, p(3) = 4, p(4) = 5, p(5) = 3。

花了10分钟理解题意,然后发现其实p就是一个个简单环,问题转化为:

把n拆成几个正整数的和(正整数可以为1),这些正整数的最小公倍数就是我们要的最大的秩

然后打暴力发现每一个正整数要么是1,要么是某个质数的某个次幂。

可以用dp

设f[I,j]表示我们处理到第i个质数、当前和为j所能获得的最大秩。则f[I,j]=max(f[i-1,j-w]*w)。(这里的w是简写)。而我们要求排列的话,只需记录一下每个状态是由哪个状态转移过来的,最后还原即可。

By kqp题解

兴奋得以为能切

然鹅...

第五步:注意事项

f的值很大很大,大过long long,所以我们要把f的值转为自然对数来做。由于自然对数是单调函数,所以比较大小的方式一毛一样。

Ps2:题解的实质在第四步。

By kqp题解

...幹

我把题解的前四步都想到了,就差第五步了,居然只给我40分。

心累啊

Ps:

其实不用自然对数也可以。由于这道题最大只有\(10^{136}\) ,用double存的下,又因为存的是乘积,一两个数的差别会有很大影响,所以把f的类型改为double即可

#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std;

long long bz[10005],l[10005],i,j,s,n,k,ans1,ans2,T;
double f[1305][10005],ans;
int from[1305][10005][3];
long long list[10005];

void write(long long x,long long y)
{
	if (from[x][y][2]!=0)
	{
		write(from[x][y][1],from[x][y][2]);
	}
	list[++list[0]]=from[x][y][0];
}

int main()
{ 
	freopen("read.in","r",stdin);
	for (i=2;i<=10000;i++)
	{
		if (!bz[i]) 
		{
			l[++l[0]]=i;
		}
		for (j=1;j<=l[0];j++)
		{
			if (l[j]*i>10000) break;
			bz[l[j]*i]=1;
			if (i%l[j]==0) break;
		}
	}
	f[0][0]=1;
	for (j=1;j<=10000;j++)
	{
		f[0][j]=1;
		from[0][j][0]=1;
		from[0][j][1]=0;
		from[0][j][2]=j-1;
	}
	for (i=0;i<=l[0]-1;i++)
	{
		for (j=0;j<=10000;j++)
		{
			for (k=l[i+1];k+j<=10000;k=k*l[i+1])
			if ((double)k*f[i][j]>f[i+1][j+k])
			{
				f[i+1][j+k]=(double)k*f[i][j];
				from[i+1][j+k][0]=k;
				from[i+1][j+k][1]=i;
				from[i+1][j+k][2]=j;
			}
		}
		for (j=0;j<=10000;j++)
		{
			if (f[i+1][j]<f[i][j])
			{
				f[i+1][j]=f[i][j];
				from[i+1][j][0]=from[i][j][0];
				from[i+1][j][1]=from[i][j][1];
				from[i+1][j][2]=from[i][j][2];
			}
		}
	}
	scanf("%lld",&T);
	while (T--)
	{
		scanf("%lld",&n);
		ans=ans1=ans2=0;
		list[0]=0;
		write(l[0],n);
		sort(list+1,list+1+list[0]);
		s=0;
		for (i=1;i<=list[0];i++)
		{
			for (j=2;j<=list[i];j++)
			{
				printf("%lld ",s+j);
			}
			printf("%lld ",s+1);
			s+=list[i];
		}
		printf("\n");
	}
	return 0;
}

ymqtql%%%

T4

比完赛又加了一道题。

我们会给定一个N×M的矩阵,你需要从这个矩阵中找出一个P×P的子矩阵,使得这个子矩阵的每一列和每一行都是回文序列。

对于所有数据 1 ≤ N, M ≤ 300

题目挺简单,先在中间补上0方便处理,预处理ma_row[i][j]和ma_col[i][j]表示以(i,j)为中心在行或列的最长回文串。再枚举点(i,j)作为整个子矩阵的中心,从(i,j)向上下左右找ma_row[i\(\pm\)x][j]和ma_col[i][j\(\pm\)x]的最小值就好了。

理论复杂度最高为O(\(8n^2\)),272ms。

而那些理论复杂度最高为O(\(n^5\))的做法都是20~30ms的。

又一道欺诈题:)

小结

  1. 认真看题
  2. 骗分最神奇,暴力出奇迹。
  3. 存特别大的,精度要求不高的数据可以用double/long double
  4. 注意空间!!!
posted @ 2019-07-07 22:29  leason_lyx  阅读(105)  评论(0编辑  收藏  举报