题解
今天第一天和二中的神仙在一起做题…但是本蒟蒻只能在电脑前看着北大神仙打架啊……人家AK我60……对OI丧失信心。
这是一套连lzw4896s都不怎么会的神仙题。
第一题——第一题(diyiti)<——你没有看错
【 题目描述】
给定两个01串,S,T(下标从0开始)。
支持如下3种操作:
修改S第i位的字符,即0->1,1->0.
修改T第i位的字符,即0->1,1->0.
查询S[a..a+l-1],T[b..b+l-1]的相似度。
相似度定义如下:
s,t两个字符串的相似度=sigma_{i=0}^{|s|-1} sim(s[i],t[i])
sim(a,b) 有四个参数p_{0,0},p_{0,1},p_{1,0},p_{1,1}。 sim(a,b)的返回值为p_{a,b}。
输入 第一行一个非空字符串S
第二行一个非空字符串T
第三行一个操作个数Q
接下来Q行,每行为”1 i”,”2 i”或”3 a b l p_{0,0} p_{0,1} p_{1,0} p_{1,1}”
输出 若干行,每行对应每个3操作的答案。
样例输入
1010
6
3 0 0 4 1 2 3 4
1 1
3 0 1 3 4 3 7 6
2 2
3 1 0 2 4 5 3 4
3 1 0 3 8 8 8 8
样例输出
10 19 7 24
提示
【数据范围】
10%, |S|,|T|<=1000,Q<=1000
10%, |S|,|T|<=50000,Q<=50000,3操作的p值都为1
10%, |S|,|T|<=50000,Q<=50000,所有操作为3操作且a,b,l相同
20%, |S|,|T|<=50000,Q<=50000,3操作的a,b值为0
50%, |S|,|T|<=100000,Q<=100000,0<=p<=50,由于数据随机生成,可以默认3操作l的期望为|S|/6
- 题目的前50%的数据比较水….所以裸暴力可以拿到(难道不是 ???)
- 题目的80%的数据比较一般,可以快读优化得到(后悔没有打快读orz)
- 对于最后面的两个点。只能说比较神仙了。完全没有板子。
- 由于匹配度与区间内的1的个数(或者说0的个数)有关所以可以利用压位和位运算来处理。
- 不保守的情况下,就来先压缩20位。
- 先将前20位所有情况下的二进制的1的个数递推求好。
- 再来处理每一个串,以任意位置为开头的20位的所压缩的数记录好。
- 更新时需要连续更新有关的20位。
- 查询时类似于分块,一段段地进行查询。对于散块的情况,可以直接暴力,对于整块的情况,只要将上下两端异或一下然后数1就可以了,而这里的1的个数已经处理好了,每块 的查询时间。
- 这样子大概就可以水过了。
- 但却实,数据可能比较水。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
void fff(){
freopen("diyiti.in","r",stdin);
freopen("diyiti.out","w",stdout);
}
const int MAXN=100010;
char S[MAXN],T[MAXN];
int mi[22],v[1048576];
int s[MAXN],t[MAXN],si[MAXN],ti[MAXN],n,m;
int Q,ans=0,a,b,l;
int v0,v1,v2,v3;
int read(){
int x=0;
char ch;
ch=getchar();
while (ch<'0'||ch>'9'&&ch!='-') ch=getchar();
while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
void solve(int li,int ri,int len){
if(len<20){
for (int i=0;i<=len-1;i++){
ans=ans+si[li+i]*v2+ti[ri+i]*v1;
if((si[li+i]==1)&&(ti[ri+i]==1)) ans=ans+(v3-v1-v2);
}
return;
}
ans=ans+v[s[li]]*v2+v[t[ri]]*v1;
ans=ans+v[s[li]&t[ri]]*(v3-v1-v2);
solve(li+20,ri+20,len-20);
}
int main(){
scanf("%s%s",S,T);
n=strlen(S),m=strlen(T);
mi[1]=1;
for (int i=2;i<=20;i++) mi[i]=mi[i-1]*2;
for (int i=0;i<n;i++)
si[i]=S[i]-'0';
for (int i=0;i<m;i++) ti[i]=T[i]-'0';
for (int i=1;i<=2*mi[20]-1;i++)
v[i]=v[i/2]+(i%2);
int sum=0;
for (int i=n-1;i>=n-20;i--) sum=sum+mi[n-i]*si[i];
s[n-20]=sum;
for (int i=n-21;i>=0;i--){
sum=sum/2;
sum=sum+mi[20]*si[i];
s[i]=sum;
}
sum=0;
for (int i=m-1;i>=m-20;i--) sum=sum+mi[n-i]*ti[i];
t[m-20]=sum;
for (int i=m-21;i>=0;i--){
sum/=2;
sum=sum+mi[20]*ti[i];
t[i]=sum;
}
scanf("%d",&Q);
while (Q--){
int tampo;
tampo=read();
if(tampo==1){
int pos;
pos=read();
for (int i=pos;i>=pos-19;i--){
if(i>=0) s[i]=s[i]^mi[i-pos+20];
}
si[pos]=1-si[pos];
}
if(tampo==2){
int pos;
pos=read();
for (int i=pos;i>=pos-19;i--){
if(i>=0) t[i]=t[i]^mi[i-pos+20];
}
ti[pos]=1-ti[pos];
}
if(tampo==3){
a=read(),b=read(),l=read();
v0=read(),v1=read(),v2=read(),v3=read();
ans=l*v0;
v1=v1-v0;
v2=v2-v0;
v3=v3-v0;
solve(a,b,l);
printf("%d\n",ans);
}
}
}
这个算法确实比较难想到…所有人都在想区间,反而没有人想到分块压位了….
第二题——第二题(dierti)
【题目描述】
询问如下多重集(数字可以重复的集合)的个数:
这个集合中有n个数字,每个数字在1..L之间,n为偶数。
这n个数字能分成n/2组,使得每组有两个数,且这两个数的积不超过c。
答案很大,对10^9+7取模。
输入 一行三个正整数,n,L,c
输出 一个整数,答案
样例输入
4 10 10
样例输出
107
提示
【数据范围】
10%, n=2,L<=1000,c<=1000
20%, n<=8,L<=10,c<=10
20%, n<=100,L<=100,c<=100
10%, n<=100,L<=500,c<=500
40%, n<=2000,L<=2000,c<=2000
- 其实暴力打对了可以打30的但手残,没有打对。10分。
- 看这个最开始以为是数位dp或者是组合题。但真的没有先到是纯的dp。
- 讲一下方程吧… 表示第i组一对为 的方案数。
- 则 表示上一组为 。
- OK那这个方程就是五维的dp了。但用脚想想就知道是不可能吧!
- 那需要进行压缩。
- 记 , 作为前面的前缀和。
- 那么就可以得出 ,因为当前状态是所有上一步当前状态的情况数总和。
- 那么就可以推出:
- 由于需要考虑重复的问题(是个集合,无序)那么就要保证不重复了。从 这样子去进行分,转移的时候也是从两边拓展往中间来分,所以方程是从两端进入的。
- 然后由于当前状态是由前一个状态转移过来的,那么空间上开个滚动可以省掉一维。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
void fff(){
freopen("dierti.in","r",stdin);
freopen("dierti.out","w",stdout);
}
const int MOD=1e9+7;
int n,l,c;
int f[55][2005];
int g[55][2005];
int main(){
// fff();
scanf("%d%d%d",&n,&l,&c);
n>>=1;
f[1][min(l,c)]=1;
int lim=min(l,(int)sqrt(c));
for (int i=1;i<=n;i++){
memcpy(g,f,sizeof(g));
for (int j=1;j<=lim;j++){
for (int k=min(l,c/j);k>=j;--k){
g[j][k]=(g[j][k]+g[j][k+1])%MOD;
}
}
for (int j=1;j<=lim;j++){
for (int k=min(l,c/j);k>=j;--k){
g[j][k]=(g[j][k]+g[j-1][k])%MOD;
}
}
swap(f,g);
}
int ans=0;
for (int i=1;i<=lim;i++){
for (int k=i;k<=l;k++){
ans=(ans+f[i][k])%MOD;
}
}
cout<<ans;
}
第三题——第三题(disanti)
【题目描述】
有一个两边点集大小都是n的二分图。他本来是一个完全二分图(即把图中的顶点分成两个集合,使得第一个集合中的所有顶点都与第二个集合中的所有顶点相连)。
每条边都有一个边权。对一个匹配,在匹配中边的边权的总和为这个匹配的价值。但是由于某些原因,这个二分图中的k条边不见了。
他想知道现在这个二分图还有几种完备匹配(如果图的所有顶点都与某匹配中的一条边相关联,则称此匹配为完备匹配)方案,并且所有完备匹配的价值的总和是多少。
input
第一行,一个正整数n。
接下来有n行,每行n个整数,第i行第j个整数w_{i,j},表示点i向另一个点集中的点j边的权值。注意点从0开始标号。
接下来一行,一个正整数k,表示有多少条边不见了。
接下来k行,每行两个整数u,v,表示点u向另一个点集中的点v边不见了。
output
一行两个整数,第一个数为完备匹配的个数,第二个数为所有完备匹配的价值总和,由于数字较大,请对10^9+7取模。
sample in
5
2 3 4 5 6
5 4 3 2 1
7 6 5 4 3
5 6 7 8 9
3 4 5 6 7
3
1 2
2 2
3 4sample out
60 1408
10%, n<=100,k=0,w_{i,j}=0
10%, n<=10,k<=20,0<=w_{i,j}<=500。
10%, n<=15,k<=20,0<=w_{i,j}<=500。
10%,n<=100,k=0,0<=w_{i,j}<=500。
10%,n<=100,k<=20,w_{i,j}=0
20%,n<=100,k<=15,0<=w_{i,j}<=500。
30%,n<=300,k<=20,0<=w_{i,j}<=500。
- 很不幸,这道题我真的不会….如果有生之年能够学会二分图的话,就来补一把题解吧。
- 贴上的是巨佬屠xc的代码(就是那个北大AK巨佬)。
#include<cstdio>
#include<algorithm>
#include<ctype.h>
#include<string.h>
#include<math.h>
using namespace std;
#define ll long long
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define travel(i,x) for(int i=h[x];i;i=pre[i])
inline char read() {
static const int IN_LEN = 1000000;
static char buf[IN_LEN], *s, *t;
return (s == t ? t = (s = buf) + fread(buf, 1, IN_LEN, stdin), (s == t ? -1 : *s++) : *s++);
}
template<class T>
inline void read(T &x) {
static bool iosig;
static char c;
for (iosig = false, c = read(); !isdigit(c); c = read()) {
if (c == '-') iosig = true;
if (c == -1) return;
}
for (x = 0; isdigit(c); c = read()) x = ((x + (x << 2)) << 1) + (c ^ '0');
if (iosig) x = -x;
}
const int OUT_LEN = 10000000;
char obuf[OUT_LEN], *ooh = obuf;
inline void print(char c) {
if (ooh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), ooh = obuf;
*ooh++ = c;
}
template<class T>
inline void print(T x) {
static int buf[30], cnt;
if (x == 0) print('0');
else {
if (x < 0) print('-'), x = -x;
for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
while (cnt) print((char)buf[cnt--]);
}
}
inline void flush() { fwrite(obuf, 1, ooh - obuf, stdout); }
const int N = 305, K = 25, P = 1000000007;
int n, ss, k, ans1, ans2, u[K], v[K], s1[N], s2[N], p[N], a[N][N];
bool vis[K], visu[N], visv[N];
void dfs(int now, int w=1, int sum1=ss, int sum2=0, int tot=0){
if(now==k){
ans1=((ll)ans1+w*p[n-tot]+P)%P;
ans2=(ans2+(ll)w*sum2*p[n-tot])%P;
if(tot!=n) ans2=(ans2+(ll)w*sum1*p[n-tot-1])%P;
ans2=(ans2<0?ans2+P:ans2);
return;
}
dfs(now+1, w, sum1, sum2, tot);
if(!visu[u[now]] && !visv[v[now]]){
visu[u[now]]=1, visv[v[now]]=1;
sum1-=s1[u[now]]+s2[v[now]]-a[u[now]][v[now]];
for(int i=0; i<now; ++i) if(vis[i]) sum1+=a[u[now]][v[i]]+a[u[i]][v[now]];
vis[now]=1, dfs(now+1, -w, sum1, sum2+a[u[now]][v[now]], tot+1), vis[now]=0;
visu[u[now]]=0, visv[v[now]]=0;
}
}
int main() {
freopen("disanti.in", "r", stdin);
freopen("disanti.out", "w", stdout);
p[0]=1;
rep(i, 1, 300) p[i]=(ll)p[i-1]*i%P;
read(n);
rep(i, 0, n-1) rep(j, 0, n-1) read(a[i][j]), s1[i]+=a[i][j], s2[j]+=a[i][j], ss+=a[i][j];
read(k);
rep(i, 0, k-1) read(u[i]), read(v[i]);
dfs(0);
return printf("%d %d", ans1, ans2), 0;
}