前言
OTL
hdu 6344 调查问卷
题目
n n n份互不相同的问卷,在整理结果的时候,他发现可以只保留其中的一部分问题,使得其中 k k k份问卷仍然是互不相同的。这里认为两张问卷是不同的,当且仅当存在至少一个被保留的问题在这两份问卷中的回答不同。问有多少个问题的子集满足以上要求
分析
然而(比赛)题目比较玄学,可能理解有问题,但是实际上还是知道的,由于问题数较少,可以用状态压缩,枚举子集,用总共的 C n 2 C_n^2 Cn2减掉重复的(可以用一个数组标记),当 ≥ \geq ≥k那么累计答案
代码
#include <cstdio>
#include <cstring>
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<13,stdin)),p1==p2?EOF:*p1++)
#define rr register
using namespace std;
int s[1000001][26]; char buf[1<<13],*p1,*p2;
inline signed in(){
rr int ans=0; rr char c=getchar();
while (c<48||c>57) c=getchar();
while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
return ans;
}
inline signed print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
signed main(){
rr int t=in();
for (rr int o=1;o<=t;++o){
printf("Case #%d: ",o);
rr int n=in(),m=in(),k1=in(),ans=0;
rr int a[n+1];
for (rr int i=1;i<=n;++i){
a[i]=0; rr char c=getchar();
while (c!='A'&&c!='B') c=getchar();
for (rr int j=1;j<=m;++j) a[i]=(a[i]<<1)+(c=='A'),c=getchar();//状态压缩
}
for (rr int i=0;i<(1<<m);++i){
rr int v[1<<m]; memset(v,0,sizeof(v)); rr int k=0;
for (rr int j=1;j<=n&&k<k1;++j) k+=j-(++v[i&a[j]]);//重复的考卷用v标记
if (k>=k1) ans++;
}
if (ans) print(ans); else putchar(48);
putchar(10);
}
return 0;
}
hdu 6345 子串查询
题目
问区间最小大写字母的个数
分析
由于大写字母只有26个,所以可以用前缀和,然后AC这道题其实是比较简单的,当然听说有 O ( n ) O(n) O(n)的方法,只能膜拜dalao了
代码
#include <cstdio>
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<13,stdin)),p1==p2?EOF:*p1++)
#define rr register
using namespace std;
int s[100001][26]; char buf[1<<13],*p1,*p2;
inline signed in(){
rr int ans=0; rr char c=getchar();
while (c<48||c>57) c=getchar();
while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
return ans;
}
inline signed print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
signed main(){
rr int t=in();
for (rr int o=1;o<=t;++o){
printf("Case #%d:\n",o);
rr int n=in(),q=in(); rr char c=getchar();
while (c<65||c>90) c=getchar();
for (rr int i=1;i<=n;++i,c=getchar())
for (rr int j=0;j<26;++j) s[i][j]=s[i-1][j]+(c==j+65);
while (q--){
rr int l=in(),r=in();
if (l>r) putchar(48);//反正加上比较舒服
else for (rr int j=0;j<26;++j)
if (s[r][j]-s[l-1][j]){//前缀和差分
print(s[r][j]-s[l-1][j]);
break;//直接退出
}
putchar(10);
}
}
return 0;
}
hdu 6348 序列计数
题目
问 k ∈ [ 1 … n ] k\in[1\dots n] k∈[1…n]时,子上升序列长度为 k k k的个数
分析
一道动态规划的题目,设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为子序列长度为
i
i
i,以j结尾的个数,那么状态转移方程是
d
p
[
i
]
[
j
]
=
∑
{
d
p
[
i
−
1
]
[
k
]
,
k
∈
[
1
…
j
−
1
]
且
a
[
k
]
<
a
[
j
]
}
,
d
p
[
1
]
[
i
]
=
1
dp[i][j]=\sum\{dp[i-1][k],k\in[1\dots j-1]且a[k]<a[j]\},dp[1][i]=1
dp[i][j]=∑{dp[i−1][k],k∈[1…j−1]且a[k]<a[j]},dp[1][i]=1
问题是这样枚举k必然
O
(
n
3
)
O(n^3)
O(n3)会超时,所以选择如何一次到位,然后就可以用树状数组维护,那么时间就变成了
O
(
n
2
l
o
g
2
n
)
O(n^2log_2n)
O(n2log2n),然而目测还是会超时,那么由于是随机数据,会等概率,所以其实实测时间复杂度在
O
(
n
n
l
o
g
2
n
)
O(n\sqrt nlog_2n)
O(nnlog2n)左右
代码
#include <cstdio>
#include <cstring>
#define rr register
#define mod 1000000007
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<13,stdin)),p1==p2?EOF:*p1++)
using namespace std;
char buf[1<<13],*p1,*p2;
inline signed in(){
rr int ans=0; rr char c=getchar();
while (c<48||c>57) c=getchar();
while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
return ans;
}
inline signed print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
signed main(){
rr int t=in();
for (rr int o=1;o<=t;++o){
printf("Case #%d: ",o);
rr int n=in(),flag=1; print(n);
rr int a[n+1],c[n+1],dp[n+1][2];//滚动数组
for (rr int i=1;i<=n;++i) a[i]=in(),dp[i][1]=1;
for (rr int j=2,now=0;j<=n;++j,now^=1){
putchar(32);
if (!flag){//只要有一个答案为0那么后面都是0
putchar(48);
continue;
}
for (rr int i=1;i<=n;++i) c[i]=0;//初始化树状数组为0
for (rr int i=1;i<=n;++i){
rr int x=a[i]-1; dp[i][now]=0;
while (x) dp[i][now]=(dp[i][now]+c[x])%mod,x-=-x&x;//更新答案
x=a[i];
while (x<=n) c[x]=(c[x]+dp[i][now^1])%mod,x+=-x&x;//那么取上一次更新树状数组
}
rr int ans=0;
for (rr int i=1;i<=n;++i) ans=(ans+dp[i][now])%mod;//计算和
if (!ans) flag=0,putchar(48); else print(ans);
}
putchar(10);
}
return 0;
}
后续
经常被ssl_xxy,ssl_lw,ssl_hjq,ssl_lrz,ssl_hzb,ssl_wyc和ssl_zyc吊打,非常不爽