【长沙集训】2017.10.10
Adore
1.1 问题描述
小 w 偶然间遇到了一个 DAG。 这个 DAG 有 m 层,第一层只有1个源点,最后一层只有1个汇点,剩下的每一层都有 k 个 节点。
现在小 w 每次可以取反第 i(1 < i < n − 1) 层和第 i + 1 层之间的连边。也就是把原本从 (i, k1) 连到 (i + 1, k2) 的边,变成从
(i, k2) 连到 (i + 1, k1)。 请问他有多少种取反的方案,把从源点到汇点的路径数变成偶数条? 答案对 998244353 取模。
1.2 输入格式
第一行两个整数 m, k。
接下来 m − 1 行, 第一行和最后一行有 k 个整数 0 或 1,剩下每行有 k 2 个整数 0 或 1,
第 (j − 1) × k + t 个整数表示 (i, j) 到 (i + 1, t) 有没有边。
1.3 输出格式
一行一个整数表示答案。
1.4 样例输入
5 3
1 0 1
0 1 0 1 1 0 0 0 1
0 1 1 1 0 0 0 1 1
0 1 1
1.5样例输出
4
1.6数据规模与约定
20% 的数据满足 n ≤ 10, k ≤ 2
40% 的数据满足 n ≤ 10^3 , k ≤ 2。
60% 的数据满足 m ≤ 10^3 , k ≤ 5。
100% 的数据满足 4 ≤ m ≤ 10^4 , k ≤ 10。
1.
这要是Noip d1t1我也不用学oi了。。。
每一层只有10个点,我们只关心每个点的奇偶情况,于是可以考虑状压dp。dp[i][j]表示第i层奇偶情况为j的答案。
暴力的写法,枚举每一层,每种情况,枚举每条边暴力地更新奇偶算转移到的状态。60分。场上zz不知道哪个地方莫名多了一个忘删掉的for循环,炸了两个点。。
优化一下,预处理出连向每个点的边的集合,用一个二进制串表示,然后每次和转移来的状态取交,判断这个数的1的个数的奇偶(这一步同样是预处理)。
然后又是垃圾卡常,,可能我真的是传说中的 真·大常数选手吧。。好不容易卡进去。。
//Twenty
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
typedef long long LL;
const LL mod=998244353;
const int maxn=1e4+299;
const int maxm=1e6+299;
int m,k,x,s,t;
using namespace std;
int a[maxn][11],b[maxn][11],ok[maxn];
int f[10005][1030];
inline int get(int &x) {
int ret=0; char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) ret=ret*10+ch-'0';
x=ret;
}
void work() {
int nn=(1<<k)-1;
for(register int i=0;i<=nn;i++) ok[i]=ok[i>>1]^(i&1);
for(register int i=3;i<=m;i++) {
for(register int j=0;j<=nn;j++) if(f[i-1][j]){
int now=0,s1=0,s2=0;
for(register int l=1;l<=k;l++) {
s1|=ok[a[i][l]&j]<<l-1;
s2|=ok[b[i][l]&j]<<l-1;
}
f[i][s1]+=f[i-1][j];
if(f[i][s1]>=mod) f[i][s1]-=mod;
if(i!=m) {f[i][s2]+=f[i-1][j];
if(f[i][s2]>=mod) f[i][s2]-=mod;}
}
}
}
int main()
{
freopen("adore.in","r",stdin);
freopen("adore.out","w",stdout);
get(m); get(k);
int now=0;
for(register int i=1;i<=k;i++) {
get(x);
if(!x) continue;
now|=(1<<i-1);
}
f[2][now]=1;
for(register int i=2;i<m-1;i++) {
for(register int j=1;j<=k;j++)
for(register int l=1;l<=k;l++) {
get(x);
if(!x) continue;
a[i+1][l]|=(1<<j-1);
b[i+1][j]|=(1<<l-1);
}
}
for(register int i=1;i<=k;i++) {
get(x);
if(!x) continue;
a[m][1]|=(1<<i-1);
}
work();
printf("%d\n",f[m][0]);
fclose(stdin);
fclose(stdout);
return 0;
}
Confess
2.1 问题描述
小w 隐藏的心绪已经难以再隐藏下去了。 小w 有 n + 1(保证 n 为偶数) 个心绪,每个都包含了 [1, 2n] 的一个子集。 现在他要找到隐藏的任意两个心绪,使得他们的交大于等于 n/ 2。
2.2 输入格式
一行一个整数 n。
接下来每行一个长度为 k 的字符串,该字符串是一个 64 进制表示,
ASCII 码为 x 的字符代 表着 x − 33,所有字符在 33 到 33 + 63 之间。
转为二进制表示有 6k 位,它的前 2n 个字符就是读入的集合,
第 i 位为 1 表示这个集合包 含 i,为 0 表示不包含。
2.3 输出格式
一行两个不同的整数表示两个集合的编号。
如果无解输出”NO Solution”。
2.4 样例输入
10
EVK#
IH=#
676"
R7,#
74S"
6V2#
O3J#
S-7$
NU5"
C[$$
3N.#
2.5 样例输出
1 2
2.6 数据规模与约定
对于 20% 的数据满足 n ≤ 100。
对于 50% 的数据满足 n ≤ 1 × 10^3。
对于 100% 的数据满足 n ≤ 6 × 10^3。
2.
t1写得太难受+语文太烂,没读懂题,暴力都没打直接输出no solution。
然而没有No solution的情况,正解就是暴力。
首先”NO Solution” 两个单词大小写不一致,所以肯定是乱 打的,不会有无解。
证明?我们不妨算一算如果随机选两个集合,他们交的期望
min( ∑2n i=1 C(ci ,2) C(n+1,2) | ∑2n i=1 ci = n(n + 1)) = n−1/2
注意 n 是偶数,所以大于 n−2/2 即可说明会有交为 n 的
由于大了一个常数,所以至少有 n 对!
随机 O(n) 对即可
标解用了bitset,开O2跑得贼快,不用bitset就又是垃圾卡常,怎么卡都卡不进,只能一边输一遍做找到答案就输都不输了才ok。。。
//Twenty
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<queue>
using namespace std;
const int maxn=6e3+5;
int n,sz,b[maxn];
char ch[60005];
bool a[maxn][2*maxn],tp[10];
int ok(int x,int y) {
int ret=0;
for(int i=1;i<=2*n;i++) {
if(a[x][i]==1&&a[y][i]==1) ret++;
if(ret>=n/2) return 1;
}
return ret>=n/2;
}
int main()
{
freopen("confess.in","r",stdin);
freopen("confess.out","w",stdout);
scanf("%d",&n);
for(register int i=1;i<=n+1;i++) {
cin>>ch;
int p=0,q=0;
while(q<=2*n)
{
int mid=ch[p++]-33;
for(int j=0;j<6;j++)
a[i][++q]=mid&(1<<j);
}
for(register int j=i-1;j>=1;j--)
if(ok(i,j)) {
printf("%d %d\n",i,j);
return 0;
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
--------------------------------------------------------------------------------------------
事实证明被卡常是某个叫random_shuffle()的函数的锅。换成sort就过了。
还有getchar会比cin快很多。常数这种东西太可怕了。
//Twenty
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<queue>
using namespace std;
const int maxn=6e3+5;
int n,sz,b[maxn],c[maxn];
char s[105];
bool a[maxn][2*maxn],tp[10];
int ok(int x,int y) {
int ret=0;
for(int i=1;i<=2*n;i++) {
if(a[x][i]==1&&a[y][i]==1) ret++;
if(ret>=n/2) return 1;
}
return ret>=n/2;
}
bool cmp(const int &x,const int &y) {
return b[x]<b[y];
}
int main()
{
freopen("confess.in","r",stdin);
freopen("confess.out","w",stdout);
srand(time(0));
cin>>n;
for(int i=1;i<=n+1;i++) {
int tot=0;
char c=getchar();
while(c<33||c>96)
c=getchar();
while(c>=33&&c<=96) {
for(int j=0;j<=5&&tot<=2*n;j++)
a[i][++tot]=(((c-33)>>j)&1);
c=getchar();
}
}
for(int i=1;i<=n+1;i++) c[i]=i,b[i]=rand();
sort(c+1,c+n+2,cmp);
for(int i=1;i<=n+1;i++) {
for(int j=i+1;j<=n+1;j++)
if(ok(c[i],c[i+1])) {
printf("%d %d\n",c[i],c[i+1]);
return 0;
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
Repulsed
3.1 问题描述
小 w 心理的火焰就要被熄灭了。 简便起见,假设小w 的内心是一棵 n − 1 条边,n 个节点的树。 现在你要在每个节点里放一些灭火器,每个节点可以放任意多个。 接下来每个节点都要被分配给一个最多 k 条边远的灭火器,每个灭火器最多能分配给 s 个节 点。 最少要多少个灭火器才能让小 w 彻底死心呢?
3.2 输入格式
第一行三个整数 n, s, k。
接下来 n − 1 行每行两个整数表示一条边。
3.3 输出格式
一行一个整数表示答案
3.4 样例输入
10 10 3
1 8
2 3
1 5
2 4
1 2
8 9
8 10
5 6
5 7
3.5 样例输出
1
3.6 数据规模与约定
对于 20% 的数据满足 n ≤ 100, k ≤ 2。
对于另外 20% 的数据满足 k = 1。
对于另外 20% 的数据满足 s = 1。
对于 100% 的数据满足 n ≤ 10^5 , k ≤ 20, s ≤ 10
至底往上贪心。
考虑一条链上去只要能管得到肯定往上放比较优,每次从下到上再在上面考虑下面的情况。
g[x][i]表示x点往下i的距离的位置还需要多少灭火器,f[x][i]表示x点往下i的距离的位置有多少没用的灭火器。
若g[x][k]还不为0,则在该点放灭火器。
考虑子树中长度为k的距离的位置和k-1距离的位置的互相配放。
到根节点后再把没用的全用了,还差的一起补上。
//Twenty
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<queue>
const int maxn=1e5+299;
int n,s,k;
using namespace std;
int ans,ecnt,fir[maxn],nxt[maxn*2],to[maxn*2];
void add(int u,int v) {
nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
}
int f[maxn][22],g[maxn][22];
void dfs(int x,int fa) {
for(int i=fir[x];i;i=nxt[i]) if(to[i]!=fa){
int v=to[i]; dfs(v,x);
for(int j=1;j<=k;j++) {f[x][j]=min(n,f[x][j]+f[v][j-1]);g[x][j]+=g[v][j-1];}
}
g[x][0]++;
if(g[x][k]) {
int tp=(g[x][k]-1)/s+1;
f[x][0]=tp*s,ans+=tp;
}
for(int i=0;i<=k;i++) {
int j=k-i;
int d=min(f[x][i],g[x][j]);
f[x][i]-=d; g[x][j]-=d;
}
for(int i=0;i<k;i++) {
int j=k-i-1;
int d=min(f[x][i],g[x][j]);
f[x][i]-=d; g[x][j]-=d;
}
}
int main()
{
freopen("repulsed.in","r",stdin);
freopen("repulsed.out","w",stdout);
scanf("%d%d%d",&n,&s,&k);
for(int i=1;i<n;i++) {
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
}
dfs(1,0);
for(int i=0;i<=k;i++)
for(int j=0;j<=k;j++) {
if(i+j<=k) {
int d=min(f[1][i],g[1][j]);
f[1][i]-=d; g[1][j]-=d;
}
}
int tot=0;
for(int i=0;i<=k;i++) {
if(g[1][i]) tot+=g[1][i];
}
ans+=(tot?(tot-1)/s+1:0);
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}