前言
这次比赛比较简单,然而T1太自信结果炸了,T3脑子坏掉又写错了,真是没办法啊,分数就白白丢掉了好多。。
匹配数
题目描述
一个匹配模式是由一些小写字母和问号’?’组成的一个字符串。当一个由小写字母组成的字符串s,长度和匹配模式长度相同,并且在对应的每一位都相等或模式串相应位置是‘?’,则称字符串s与这个模式相匹配。例如:”abc”与”a?c”匹配地,但不与”a?b”或”abc?”相匹配。
现给你 M 个匹配模式,它们长度相同,问恰好与其中有 K 个模式相匹配的字符串有多少个?(答案模1,000,003)
输入格式
第一行,两个整数 M K。
下面有M行字符串,表示M个匹配模式。
1<= M <= 15
模式长度len满足:1 <= len<= 50
1 <= K <=M
模式中只含小写英文字母和 ‘?’
输出格式
只一行,一个整数(模1000003之后)。
输入样例
样例1:
2 2
a?
?b
样例2
1 1
?????
输出样例
样例1:
1
样例2:
881343
(注:881343 = 26^5 mod 1000003。)
题解(容斥原理)
这题可以用很暴力的状压dp切掉,但是优美的方法是容斥原理。
就是那个被大佬们成为广义容斥原理的东东。
首先注意题目求的是恰好k个匹配。我们枚举出所有状态,然后看看有几个匹配,假如有k个匹配就加进答案。但是可能匹配有多,假如匹配了k+1个就要减,但是减多了k+2的加回来。于是就是一个容斥原理了。
但是广义在于哪里呢?一开始我错了,原因在于假如匹配了k+1的方案数,对k答案的贡献是要被减去的,但减去可不止一个!设方案为v,次数就是 Ckk+1 ,于是当前匹配了p个模式,贡献就是 Ckp∗v∗sign 。其中当p-k为偶数时sign=1。当然p < <script type="math/tex" id="MathJax-Element-3"><</script>k不做。剩下的一堆就是细节的处理,具体是怎样算出v,这个很简单,看看有无矛盾,统计?个数就行了。
预处理出组合数会更好。我一开始80分因为取模的时候,可能是负数取模,忘了模数再取模了!QAQ!
时间一点也不复杂。
代码
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#define maxm 20
#define maxl 60
#define mods 1000003
using namespace std;
int m, k, ans;
int Len[maxm];
int C[maxm][maxm];
char s[maxm][maxl], cc[maxl];
bool vis[maxm];
void Da(){
C[0][0] = 1;
for(int i = 1; i <= m; i++){
C[i][0] = 1;
for(int j = 1; j <= i; j++)
C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mods;
}
}
int main(){
scanf("%d%d", &m, &k);
Da();
for(int i = 1; i <= m; i++){
scanf("%s", &s[i]);
Len[i] = strlen(s[i]);
}
for(int i = 1; i < (1<<m); i++){
int temp = i, b = 1, cnt = 0, sign;
for(int j = 1; j <= m; j++) vis[j] = false;
while(temp){
if(temp & 1){
vis[b] = true;
cnt ++;
}
temp >>= 1;
b ++;
}
if(cnt < k) continue;
else if((cnt - k) & 1) sign = -1;
else sign = 1;
int len = -1;
bool ok = true;
for(int j = 1; j <= m; j++){
if(!vis[j]) continue;
if(len == -1) len = Len[j];
else if(len != Len[j]){
ok = false;
break;
}
}
if(!ok) continue;
for(int j = 0; j < len; j++) cc[j] = '?';
ok = true;
for(int j = 1; j <= m; j++){
if(!vis[j]) continue;
for(int jj = 0; jj < len; jj++){
if(s[j][jj] == '?') continue;
if(cc[jj] == '?') cc[jj] = s[j][jj];
else if(cc[jj] != s[j][jj]){
ok = false;
break;
}
}
if(!ok) break;
}
if(!ok) continue;
int res = 1;
for(int j = 0; j < len; j++) if(cc[j] == '?') res = res * 26 % mods;
res = 1LL * res * C[cnt][k] % mods;
ans = (ans + res * sign + mods) % mods;
}
printf("%d\n", ans);
return 0;
}
统计子串
题解(乱搞)
枚举第一段的长度和头,然后右移过程中由于固定了长度,所以进一个出一个,第三维单调。于是 O(n2) 。
虽然很水,但是有时候枚举开头结尾换成枚举长度的方法是很好的,以长度为阶段使这题变得简单。处理两点间的位置也可以记相对距离而不是具体坐标。这种方法要牢记。
ps:这题暴力好像比正解跑得快,数据真是感人。
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <cmath>
#include <iostream>
#define maxn 5005
using namespace std;
int D, ans, len;
char s[maxn];
int main(){
scanf("%d", &D);
scanf("%s", &s);
len = strlen(s);
for(int L = 1; L <= (len>>1); L++){
int cnt = 0;
for(int i = 0; i < L; i++)
if(s[i] != s[i+L]) cnt ++;
if(cnt <= D) ans ++;
for(int i = 1; i+L-1+L < len; i++){
if(s[i-1] != s[i+L-1]) cnt --;
if(s[i+L-1] != s[i+L-1+L]) cnt ++;
if(cnt <= D) ans ++;
}
}
printf("%d\n", ans);
return 0;
}
树环转换
题目描述
给定一棵N个节点的树,去掉这棵树的一条边需要消耗值1,为这个图的两个点加上一条边也需要消耗值1。树的节点编号从1开始。在这个问题中,你需要使用最小的消耗值(加边和删边操作)将这棵树转化为环,不允许有重边。
环的定义如下:
(1)该图有N个点,N条边。
(2)每个顶点的度数为2。
(3)任意两点是可达的。
树的定义如下:
(1)该图有N个点,N-1条边。
(2)任意两点是可达的。
对于20%的数据,有1≤N≤10。
对于100%的数据,有1≤N≤1000000。
输入格式
第一行是一个整数N代表节点的个数。
接下来N-1行每行有两个整数U, V(1 ≤ U, V ≤ N),表示双向边(U, V)
输出格式
输出把树转化为环的最小消耗值。
输入样例
4
1 2
2 3
2 4
输出样例
3
题解(贪心+树形DP(bfs))
这是一道好题,我想了好久还码错了。
我们发现将树变成环其实就是先拆成链再连起来。其中一些边不动,另外一些变要断掉然后再和其他边连成链。那我们怎么知道那些边是要切断,哪些边要保留呢?这里我们考虑贪心。
如果一个节点它的儿子没有,不考虑。有1个儿子,肯定不要断掉下面的边。我们先树形DP一下求出所有节点的儿子数siz,这里的儿子要满足上述的情况,记为f。一开始我的错误就是在这里忘了判这个,样例居然还过了。
然后我们再dfs一遍,如果当前的节点的f<2,不用割,如果f>=2,需要割掉自成链的个数就是f-1,因为我们保留其中跨根的一条链,还要删掉与根父亲的连边。为什么不考虑和父亲连边呢?因为这样不会优(贪心)。于是我们只考虑能连到这里的儿子,然后考虑保留一条链,于是我们贪心地算出了最小链操作数,答案就是算出来的*2+1(每个操作包括断与连,最后要连成环要+1)。
这样我们每次连到一个点的儿子数是固定的,然后直接计算就可以了。注意如果是根节点的话要特判。
时间复杂度 O(n) 。
ps:n太大爆栈,bfs保平安。
代码
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstring>
#define maxn 1000010
using namespace std;
int n;
int ans;
int head_p[maxn], f[maxn], fa[maxn], q[maxn], cur = -1;
struct Adj{int next, obj;} Edg[maxn<<1];
void Insert(int a, int b){
cur ++;
Edg[cur].next = head_p[a];
Edg[cur].obj = b;
head_p[a] = cur;
}
void bfs1(){
int head = 0, tail = 0;
q[0] = 1;
while(head <= tail){
int now = q[head++];
for(int i = head_p[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj;
if(v == fa[now]) continue;
fa[v] = now;
q[++tail] = v;
}
}
for(int i = tail; i > 0; i--){
int now = q[i];
if(f[now] < 2) f[fa[now]] ++;
}
}
void bfs2(){
int head = 0, tail = 0;
q[0] = 1;
while(head <= tail){
int now = q[head++];
if(f[now] >= 2){
ans += f[now] - 1;
if(now == 1) ans --;
}
for(int i = head_p[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj;
if(v == fa[now]) continue;
q[++tail] = v;
}
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++) head_p[i] = -1;
int a, b;
for(int i = 1; i < n; i++){
scanf("%d%d", &a, &b);
Insert(a, b);
Insert(b, a);
}
bfs1();
bfs2();
printf("%d\n", ans << 1 | 1);
return 0;
}
总结
这次比赛本来以为分数可以很理想,结果T1粗心了,T3脑抽了。这主要是我在家码完程序就去看番了,以后一定要检查代码,有空多写对拍,手画数据很必要(像T3)。
还有,别天真地以为smoj是无限栈的。