【USACO3.1.5】联系 位运算/KMP/AC自动机

吐槽:这题的输出真恶心恶心恶心恶心恶心*10000……


TLE方法:

暴力KMP,把所有的100 101 111之类的方案,全部和原串匹配一次…… 然后排序输出。

Compiling...
Compile: OK

Executing...
   Test 1: TEST OK [0.008 secs, 9424 KB]
   Test 2: TEST OK [0.003 secs, 9424 KB]
   Test 3: TEST OK [0.032 secs, 9424 KB]
   Test 4: TEST OK [0.065 secs, 9424 KB]
  > Run 5: Execution error: Your program (`contact') used more than
        the allotted runtime of 1 seconds (it ended or was stopped at
        1.674 seconds) when presented with test case 5. It used 9420 KB of
        memory. 
/*
TASK:contact
LANG:C++
*/

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

bool text[200020];
int L, R, n, tlen=0, slen, next[50];
bool son[50];

void init()
{
	scanf("%d%d%d\n", &L, &R, &n);
	char ch;
	while (1)
	{
		ch = getchar();
		if (ch == '\n')	continue;
		if (ch == EOF)	break;
		text[tlen++] = ch - '0';
	}
}

void cal(int k, int w) //k这个数字,转化为w位数字
{
	slen = w - 1;
	memset(son,0,sizeof(son));
	while (k)
	{
		son[slen--] = k % 2;	
		k /= 2;
	}
	slen = w;
}

int kmp(bool *son, bool *text, int slen, int tlen)
{
	int i = 0, j = -1;
	int ans = 0;
	next[0] = -1;
	while (i != slen)
	{
		if (j == -1 || son[i] == son[j])	next[++i] = ++ j;	
		else j = next[j];
	}
	i = 0, j = 0;
	while (i != tlen)
	{
		if (j == -1 || text[i] == son[j])	++i, ++ j;	
		else j = next[j];
		if (j == slen)
		{
			++ ans;
			j = next[j];
		}
	}
	return ans;
}

struct pack
{
	int key;
	int k, w;
}a[500000];
int tail=0;

bool operator < (pack A, pack B)
{
	if (A.key != B.key)	return A.key > B.key; //首先是重复次数多的
	if (A.w != B.w) return A.w < B.w;
	if (A.k != B.k)	return A.k < B.k;
}

void doit()
{
	for (int i = L; i <= R; ++ i)//穷举位数
	{
		for (int j = 0; j <= (1 << i) - 1; ++ j)//穷举十进制数字,然后换成二进制
		{
			cal(j, i);//	
			a[tail].key = kmp(son, text, slen, tlen);
			if (a[tail].key == 0)	continue;
			a[tail].k = j;
			a[tail].w = i;
			++tail;
		}
	}
	sort(a, a + tail);
	int tmp = -1,t=0, now=0; //最后一次出现的循环次数的数值, 输出了多少个不同的循环,  当前节点
	int output_num;
	while (now != tail)
	{
		if (a[now].key != tmp)//新的重复次数	
		{
			if (t == n)	break;
			if (tmp != -1)	printf("\n");
			printf("%d", tmp = a[now].key);
			putchar('\n');
			++ t;	
			output_num = 0;
		}else{
			if (output_num != 6)	putchar(' ');
			else 
			{
				putchar('\n');
				output_num = 0;
			}
		}
		cal(a[now].k, a[now].w);
		for (int i = 0; i != a[now].w; ++ i)	putchar(son[i]+'0');
		++ output_num;
		++now;
	}
	putchar('\n');
}

int main()
{
	freopen("contact.in","r",stdin);
	freopen("contact.out","w",stdout);
	init();
	doit();
	return 0;
}





AC方法1:

位运算。 把原串看成是一个二进制串。 把后面1位,2位,3位…… 都截取下来,换成十进制K,然后给b[k][i] (十进制是K,二进制是i位),保存进b数组,然后给b数组排序输出即可。


要点1:    0 00 000是不一样的串, 所以可以要记录一个数字到底是几位。

要点2:    为了省事,在给sort附加CMP信息的时候,可以在3个关键字上做手脚,达到排序后直接输出即可,不需要再做任何调整。 (第一关键字正序,第二第三关键字倒序)


速度很优秀

Executing...
   Test 1: TEST OK [0.003 secs, 9816 KB]
   Test 2: TEST OK [0.000 secs, 9816 KB]
   Test 3: TEST OK [0.003 secs, 9816 KB]
   Test 4: TEST OK [0.003 secs, 9816 KB]
   Test 5: TEST OK [0.005 secs, 9816 KB]
   Test 6: TEST OK [0.019 secs, 9816 KB]
   Test 7: TEST OK [0.014 secs, 9816 KB]

All tests OK.
/*
TASK:contact
LANG:C++
*/

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

int L, R, n, slen, tlen=0;
int son[13];

inline void cal(int k, int w) //k这个数字,转化为w位数字
{
	slen = w - 1;
	memset(son,0,sizeof(son));
	while (k)
	{
		son[slen--] = k % 2;	
		k /= 2;
	}
	slen = w;
}

struct pack
{
	int key;  //出现的次数
	int k, w; //模拟的是k,位数是w
}a[140000];
int tail=0;

int b[9000][15]={0};

inline bool operator < (pack A, pack B)
{
	if (A.key != B.key)	return A.key > B.key; //首先是重复次数多的
	if (A.w != B.w) return A.w < B.w;
	if (A.k != B.k)	return A.k < B.k;
}

void doit()
{
	for (int i = L; i <= R; ++ i)
	{
		for (int j = 0; j <= (1 << i) - 1; ++ j)	
		{
			if (b[j][i] == 0)	continue;
			a[tail].key = b[j][i];
			a[tail].w = i;
			a[tail++].k = j;
		}
	
	}
	sort(a, a + tail);
	int tmp = -1,t=0, now=0; //最后一次出现的循环次数的数值, 输出了多少个不同的循环,  当前节点
	int output_num;
	while (now != tail)
	{
		if (a[now].key != tmp)//新的重复次数	
		{
			if (t == n)	break;
			if (tmp != -1)	printf("\n");
			printf("%d", tmp = a[now].key);
			putchar('\n');
			++ t;	
			output_num = 0;
		}else{
			if (output_num != 6)	putchar(' ');
			else 
			{
				putchar('\n');
				output_num = 0;
			}
		}
		cal(a[now].k, a[now].w);
		for (int i = 0; i != a[now].w; ++ i)	putchar(son[i]+'0');
		++ output_num;
		++now;
	}
	putchar('\n');
}
inline void check(int tot, int wei) //tot这个数字,来检测所有wei位(二进制下)的数字
{
	int s = (1 << wei);
	int t = tot & ((1 << wei) - 1); // tot的xxx
	b[t][wei] ++;
}

int main()
{
	freopen("contact.in","r",stdin);
	freopen("contact.out","w",stdout);
	scanf("%d%d%d\n", &L, &R, &n);
	char ch;
	int tot = 0;
	while (1)
	{
		ch = getchar();
		if (ch == '\n')	continue;
		if (ch == EOF)	break;
		++tlen;
		tot <<= 1;	
		tot += ch=='0'?0:1; //新的数字插入
		for (int i = L; i <= min(R, tlen); ++ i)	check(tot, i);	//tot长度为i
	}
	doit();
	return 0;
}

方法3: AC自动机


因为AC自动机拥有多串匹配功能,所以嘛~ 把长度范围内的二进制串都放进trie里,然后直接把后来的串放在AC自动机里跑一下就行了~ 速度也非常快。2个程序在速度差不多……   方法4,我实在想不到了……别人程序比我慢,我猜是getchar的区别导致的


Compiling...
Compile: OK

Executing...
   Test 1: TEST OK [0.008 secs, 5676 KB]
   Test 2: TEST OK [0.008 secs, 5676 KB]
   Test 3: TEST OK [0.011 secs, 5932 KB]
   Test 4: TEST OK [0.008 secs, 5676 KB]
   Test 5: TEST OK [0.024 secs, 5932 KB]
   Test 6: TEST OK [0.032 secs, 5676 KB]
   Test 7: TEST OK [0.032 secs, 5932 KB]

All tests OK.
/*
TASK:contact
LANG:C++
*/
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <cstring>
using namespace std;

int L, R, n, slen, tlen=0;
int son[13];

inline void cal(int k, int w) //k这个数字,转化为w位数字
{
	slen = w - 1;
	memset(son,0,sizeof(son));
	while (k)
	{
		son[slen--] = k % 2;	
		k /= 2;
	}
	slen = w;
}

struct pack
{
	int key;  //出现的次数
	int k, w; //模拟的是k,位数是w
}a[140000];
int tail=0;

int b[9000][15]={0};

inline bool operator < (pack A, pack B)
{
	if (A.key != B.key)	return A.key > B.key; //首先是重复次数多的
	if (A.w != B.w) return A.w < B.w;
	if (A.k != B.k)	return A.k < B.k;
}

void doit()
{
	for (int i = L; i <= R; ++ i) //排序
	{
		for (int j = 0; j <= (1 << i) - 1; ++ j)	
		{
			if (b[j][i] == 0)	continue;
			a[tail].key = b[j][i];
			a[tail].w = i;
			a[tail++].k = j;
		}
	
	}
	sort(a, a + tail);
	int tmp = -1,t=0, now=0; 
	int output_num;
	while (now != tail)  //恶心输出
	{
		if (a[now].key != tmp)
		{
			if (t == n)	break;
			if (tmp != -1)	printf("\n");
			printf("%d", tmp = a[now].key);
			putchar('\n');
			++ t;	
			output_num = 0;
		}else{
			if (output_num != 6)	putchar(' ');
			else 
			{
				putchar('\n');
				output_num = 0;
			}
		}
		cal(a[now].k, a[now].w);
		for (int i = 0; i != a[now].w; ++ i)	putchar(son[i]+'0');
		++ output_num;
		++now;
	}
	putchar('\n');
}

struct node
{
	int flag;
	int k, w;
	node *fail;
	node *c[2];
	node()
	{
		flag = k = w = 0;
		fail = NULL;
		c[0] = c[1] = NULL;
	}
};


struct AC_DFA
{
	int size;
	queue<node*>q;
	node *root;

	AC_DFA()
	{
		root = new node;	
	}

	inline void ins(int *word, int len, int k)
	{
		node *now = root;
		for (int i = 0; i != len; ++ i)
		{
			if (!now -> c[word[i]])	
				now -> c[word[i]] = new node;
			now = now -> c[word[i]];
		}
		now -> flag = 1; //这里有单词
		now -> k = k; //
		now -> w = len;
	}

	void setfail()
	{
		q.push(root);
		root -> fail = NULL;
		while (!q.empty())
		{
			node *now = q.front();		
			q.pop();
			for (int i = 0; i != 2; ++ i)
			{
				if (now -> c[i])	
				{
					node *p = now -> fail;	
					while (p && !p -> c[i])	p = p -> fail;
					now -> c[i] -> fail = p ? p -> c[i] : root;
					q.push(now -> c[i]);
				}else now -> c[i] = now == root ? root : now -> fail -> c[i];
			}
		}
	
	}
}ac;

void init()
{
	scanf("%d%d%d\n", &L, &R, &n);
	for (int i = L; i <= R; ++ i)
	{
		for (int j = 0; j  != (1 << i) ; ++ j)
		{
			cal(j, i);	
			ac.ins(son, i, j);
		}
	}
	ac.setfail();
}

int main()
{
	freopen("contact.in","r",stdin);
	freopen("contact.out","w",stdout);
	init();
	char ch;
	node *now = ac.root, *tmp;
	while (1)
	{
		ch = getchar();
		if (ch == '\n')	continue;
		if (ch == EOF)	break;
		now = now -> c[ch - '0'];
		tmp = now;
		while (tmp && tmp -> flag)
		{
			b[tmp -> k][tmp -> w] ++;
			tmp = tmp -> fail;	
		}
	}
	doit();
	return 0;
}


你可能感兴趣的:(【USACO3.1.5】联系 位运算/KMP/AC自动机)