2019百度之星初赛 P1002 度度熊与排列 (二分图最大匹配)

度度熊与排列

Accepts: 1100

Submissions: 3486

Time Limit: 2000/1000 MS (Java/Others)

Memory Limit: 32768/32768 K (Java/Others)

Problem Description

度熊有一个机器,这个机器有一个 1∼M1 \sim M1∼M 的排列 p[1..M]p[1..M]p[1..M] 当作参数,若丢进一个长度为 MMM 的字符串,此机器会将此字符串重新排列后再输出,重新排列的方式为:原本第 iii 个位置的字符会变到第 p[i]p[i]p[i] 个位置。

举例来说,当 M=3M = 3M=3,p[1]=3,p[2]=1,p[3]=2p[1]=3,p[2]=1,p[3]=2p[1]=3,p[2]=1,p[3]=2,那么丢 "abc" 进入这个机器后,机器会输出"bca";若丢进的是 "ded",那么机器会输出 "edd"。

某天,度熊不小心忘记这个机器的参数了,只记得参数的长度是 MMM,于是他丢了 NNN 长度为 MMM 的字符串进去,并记录下对于每个字符串机器的输出结果,请你根据这些结果,帮度熊找回这个机器的参数。若有多组参数都满足度熊的记录,请输出字典序最小的排列作为参数。若并不存在任何参数满足度熊的记录,请输出 −1-1−1。

注:对于两个相异的排列a: a[1..M]a[1..M]a[1..M] 和 b[1..M]b[1..M]b[1..M],我们称 aaa 比 bbb 小当且仅当 存在一个 iii,满足对于所有小于 iii 的 jjj 都有 aj=bja_j = b_ja​j​​=b​j​​ 且 ai<bia_i < b_ia​i​​<b​i​​。

Input

有多组询问,第一行包含一个正整数 TTT 代表有几组询问。

每组询问的第一行包含两个正整数 N,MN, MN,M,分别代表度熊丢进机器的字符串数目以及参数的长度。接下来还有 2×N2 \times N2×N 行,每行有一个长度为 MMM 的字符串,当中的第 2×i−12 \times i - 12×i−1 行的字符串代表度熊丢进去机器的第 iii 个字符串,而第 2×i2 \times i2×i 行的字符串代表机器对于第 iii 个字符串的输出结果。

  • 1≤T≤1001 \le T \le 1001≤T≤100

  • 1≤N≤201 \le N \le 201≤N≤20

  • 1≤M≤501 \le M \le 501≤M≤50

  • 字符串由英文小写字母('a' 至 'z') 组成

Output

对于每一个询问,输出一行,若不存在任何参数满足度熊的记录,这行只包含一个整数 −1-1−1。否则这行包含一个排列,代表此机器所有可能的参数中字典序最小的那个。

Sample Input

Copy

4
1 3
abc
bca
2 4
aaab
baaa
cdcc
cccd
3 3
aaa
aaa
bbb
bbb
ccc
ccc
1 1
a
z

Sample Output

Copy

3 1 2
2 4 3 1
1 2 3
-1

Note
第一组询问中, p[1]=3,p[2]=1,p[3]=2p[1]=3,p[2]=1,p[3]=2p[1]=3,p[2]=1,p[3]=2 是唯一的机器可能的参数。

第二组询问中, p=[2,4,3,1]p=[2,4,3,1]p=[2,4,3,1] 和 p=[3,4,2,1]p=[3,4,2,1]p=[3,4,2,1] 都是机器可能的参数,不过 [2,4,3,1][2,4,3,1][2,4,3,1] 的字典序比 [3,4,2,1][3,4,2,1][3,4,2,1] 还小,故必须输出 2,4,3,1。 

题目大意 : 有T组样例, 每组样例输入一个N和一个M, 表示有N对字符串, M表示每个字符串的长度,每对的第一个字符串表示初始字符串, 第二个字符串表示变化过的字符串, (p【1】 = 2表示第一个字符移到第二个字符那), 输出是否有可行的方案,如果有, 输出字典序最小的, 否则输出-1

思路 : 首先对每个字符可能出现的位置进行保存, 初始化所有字符可以移到任意位置, 然后根据每次输入的模板串和匹配串, 不断更新, 取他们的交集,因为只要有一个不满足肯定其他的都不能满足,如果所有字符都只有一个的话并且有方案的话, 最后形成的二分图一定是满足一个点只连另一边的一个点, 如果有多个字符相同,则形成一个点连接多个(其实这句话是废话),然后直接跑一遍二分图最大匹配就可以了, 取字典序最小的话, 只要倒过来匹配就解决了, 因为匈牙利算法是如果当前的点已经匹配, 就把它连到另一个可以匹配的地方, 会往字典序大的地方连,前面的连大的肯定反了,这就是为什么需要倒着匹配

Accepted code

#include<bits/stdc++.h>
using namespace std;

#define sc scanf
#define ls rt << 1
#define rs ls | 1
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define MEM(x, b) memset(x, b, sizeof(x))
#define lowbit(x) ((x) & (-x))
#define P2(x) ((x) * (x))

typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e3 + 10;
const int INF = 0x3f3f3f3f;
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % MOD; b >>= 1; t = (t*t) % MOD; }return r; }

int n, m, ans, T;
int pre[MAXN], p[MAXN][MAXN];
bool vis[MAXN];
string str, s;
set <int> st[MAXN];
void init() {
	MEM(p, 0); MEM(pre, 0); ans = 0;
}
bool find_(int x) {
	for (int i = 0; i < m; i++) {
		if (p[x][i] && !vis[i]) {
			vis[i] = 1;
			if (!pre[i] || find_(pre[i])) {
				pre[i] = x;
				return true;
			}
		}
	}
	return false;
}

int main()
{
	cin >> T;
	while (T--) {
		sc("%d %d", &n, &m);
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < m; j++) st[i].insert(j);  //初始化,所有都可连
		}
		while (n--) {
			map <char, vector <int> > mp; // 存每个字符可以连的
			cin >> s >> str; 
			for (int i = 0; i < SZ(str); i++) mp[str[i]].push_back(i);
			for (int i = 0; i < SZ(s); i++) {
				set <int> ss;
				for (auto it : mp[s[i]]) { // 取交集
					if (st[i].find(it) != st[i].end())
						ss.insert(it);
				}
				st[i] = ss; // 更新交集
			}
		}
		init();
		for (int i = 0; i < m; i++) {
			for (auto it : st[i]) p[it + m][i] = 1; // 建图
		}
		for (int i = 2 * m - 1; i >= m; i--) { // 反向
			MEM(vis, 0); 
			if (find_(i)) ans++;
		}
		if (ans != m) { cout << -1 << endl; continue; }
		for (int i = 0; i < m - 1; i++) cout << pre[i] - m + 1 << " ";
		cout << pre[m - 1] - m + 1 << endl;
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值