T1 串串串
题目描述
你有两个长度为 (n, m) 的 (01) 串 (S, T)。
有 (Q) 次询问,每次询问给出 (l_1, r_1, l_2, r_2),其中 (r_1 - l_1 + 1 = r_2 - l_2 + 1) 令 (a = S[l_1 dots r_1]), (b = T[l_2 dots r_2]),你需要求出 (a eq b) 的位置个数对 (2) 取模。
(n, m, q leq 2 imes 10^5)
solution
本题的点在对 (2) 取模上。
(30) 分暴力就不说了。
(100) 分
算法一:
可以发现,交换 (a) 中任意两个位置,答案是不变的,交换 (b) 中任意两个位置也一样。
那么我们可以将 (a, b) 中得 (0) 放在前面,(1) 放在后面,答案即为 (a) 中 (1) 的个数和 (b) 中 (1) 的个数之差对 (2) 取模的结果。
时间复杂度 (O(n + m + 1))
std
#include<iostream>
#include<cstdio>
#include<cassert>
using namespace std;
const int N=200005;
int n,m,Q;
char s[N],t[N];
int a[N],b[N];
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",s+1), scanf("%s",t+1);
for(int i=1;i<=n;i++) a[i]=a[i-1]+(s[i]=='1');
for(int i=1;i<=m;i++) b[i]=b[i-1]+(t[i]=='1');
scanf("%d",&Q);
while(Q--)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(((a[r1]-a[l1-1])%2)==((b[r2]-b[l2-1])%2)) printf("0
");
else printf("1
");
}
return 0;
}
算法二
不难发现答案就是 (a) 和 (b) 异或起来 (1) 的个数,如果为奇数就为 (1), 为偶数就为 (0)。
发现奇数个 (1) 异或起来正好等于 (1),偶数个异或起来等于 (0),所以直接两个区间的异或值异或起来就是答案。
复杂度 (O(m + n))
code
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 2e5 + 5;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int preb[MAXN], prea[MAXN], n, m, a[MAXN], b[MAXN], Q;
char s[MAXN], t[MAXN];
int main(){
n = read(), m = read();
scanf("%s%s", s + 1, t + 1);
for (int i = 1; i <= n; i++) a[i] = s[i] - '0', prea[i] = prea[i - 1] ^ a[i];
for (int i = 1; i <= m; i++) b[i] = t[i] - '0', preb[i] = preb[i - 1] ^ b[i];
Q = read();
while(Q--) {
int l = read(), r = read(), L = read(), R = read();
int ret = prea[r] ^ prea[l - 1], res = preb[R] ^ preb[L - 1];
int Ans = ret ^ res;
cout<<Ans<<"
";
}
puts("");
return 0;
}
T2 方格计数
题目描述
在左下角是 ((0,0)),右上角是 ((W,H)) 网格上,有 ((W+1) imes (H+1)) 个格点。
现在要在格点上找 (N) 个不同的点,使得这些点在一条直线上。并且在这条直线上,相邻点之间的距离不小于 (D)。求方案数模 (10^9+7)
(1 leq N leq 50, W, H, D leq 500, T leq 20)
solution
求方案数一开始想的是枚举直线,然后找出直线上的所有的点,然后 (dp) 求方案。但是现实是我不会枚举直线,暴力枚举直线上的点会炸掉,然后我就爆 (0) 了。
正解
知识点:组合数
网格图上,一条直线上的两个点,(gcd(|x_1 - x_2|, |y_1, y_2|) - 1) 就是直线上两点间点的数目。
任意两个元素间隔大于等于 (k) 的组合数,在 (A) 中选 (B) 个位置,要求每相邻位置要隔出至少 (C) 个空位置的方案。
相邻的有 (B-1) 对,所以空 ((B-1)C) 个,去掉这些位置就变成普通的选位置了,答案就是 (C(A-(B-1)C,B))
30pts
虑枚举两个端点,强制两个端点选,令 (a) 为两个端点之间 (x) 轴上的距离,(b) 为两个端点 (y) 轴上的距离,那么这里面可以选择的点的个数有 (g=gcd(a,b)) 个。我们要求 (N-2) 个小球(强制两个端点选),需要放到 (g) 个盒子里,相邻两个小球的盒子编号差至少为 (k),方案数为
复杂度 (O(TW^2H^2))
100pts
延续 (30) 分的思路,发现对于相同的 (a,b) 方案数也是相同的,考虑枚举 (a,b) 跟 (30) 分一样做,最后再乘个 ((W-a+1) imes (H-b+1)) 就好了,时间复杂度 (O(TWH))。
复杂度 (O(TWH))
code
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 2000 + 5;
const int mod = 1e9 + 7;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int T, n, w, h, d, C[MAXN][MAXN];
double dis(int a, int b, int x, int y) {
return sqrt((y - b) * (y - b) + (x - a) * (x - a));
}
int gcd(int a, int b){return b == 0 ? a:gcd(b, a % b);}
void Pre(){
C[0][0] = 1;
for (int i = 1; i <= 2000; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
}
}
long long work(int a, int b){
if(a == 0 && b == 0) return 0;
int g = gcd(a, b);
int k = ceil(d / dis(0, 0, a / g, b / g));//相邻连的两点的编号差
if(k * (n - 1) > g) return 0;
int res = C[g + 1 - 2 * k - (k - 1) * (n - 3)][n - 2];
if(a != 0 && b != 0) res = (res + res) % mod;
res = (long long)res *(w - a + 1) * (h - b + 1) % mod;
return res;
}
int main() {
T = read();
Pre();
while(T--) {
n = read(), w = read(), h = read(), d = read();
if(n == 1) {
printf("%d
", (w + 1) * (h + 1));
continue;
}
int Ans = 0;
for (int i = 0; i <= w; i++)
for (int j = 0; j <= h; j++) Ans = (Ans + work(i, j)) % mod;
printf("%d
", Ans);
}
system("pause");
return 0;
}
T3 树数树
题目描述
牛牛有一棵 (n) 个点的有根树,根为 (1)。
我们称一个长度为 (m) 的序列 (a) 是好的,当且仅当:
-
(forall i in (1, m], a_i) 为 (a_{i - 1}) 的祖先或 (a_{i - 1}) 是 (a_i) 的祖先;
-
(forall 1 leq i < j leq m, a_i eq a_j)
(n leq 10^5)
solution
错把祖先当作父亲,自裁 /kk
算法一:
令 (f_{u, i}) 表示 (u) 和 (u) 的子树中,允许使用子树外 (i) 个祖先所得到的最长上升长度是多少,转移相当于各个儿子的一个(max) 卷积。
时间复杂度:(O(n^2)) 期望得分 (30)
算法二:
可以发现,一个节点 (u) 可以将子树中的两个序列拼成一个序列,且我们在处理完 (u) 的父亲的时候 (u) 的状态已经不用管了,我们可以用堆维护出 (u) 和 (u) 的子树中的点能组成的序列,转移相当于是将所有子树的堆合并成一个,然后取出其中最大的两个合并成一个。
可以用启发式合并或者可并堆维护这个过程。
时间复杂度 (O(nlog^2n) sim O(nlogn))
下面是启发式合并的代码:
/*
work by:Ariel_
Sorce:
Knowledge:启发式合并
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 2e5 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int T, n, id[MAXN];
struct edge{int nxt, v;}e[MAXN << 1];
int E, head[MAXN];
void add_edge(int u, int v) {
e[++E] = (edge){head[u], v};
head[u] = E;
}
void Clear() {
memset(head, 0, sizeof head);
E = 0;
}
priority_queue<int> q[MAXN];
int Merge(int x, int y) {
if(q[x].size() > q[y].size()) swap(x, y);
while(!q[x].empty()) {
int u = q[x].top(); q[x].pop();
q[y].push(u);
}
return y;
}
void dfs(int x, int fa) {
id[x] = x;
while(!q[id[x]].empty())q[id[x]].pop();
for(int i = head[x]; i; i = e[i].nxt) {
int v = e[i].v;
if(v == fa) continue;
dfs(v, x);
id[x] = Merge(id[x], id[v]);
}
if(q[id[x]].empty()) q[id[x]].push(1);
else {
int w = q[id[x]].top(); q[id[x]].pop();
if(!q[id[x]].empty()) w += q[id[x]].top(), q[id[x]].pop();
q[id[x]].push(w + 1);
}
}
int main(){
T = read();
while(T--) {
n = read();
Clear();
for (int i = 1; i < n; i++) {
int u = read(), v = read();
add_edge(u, v), add_edge(v, u);
}
dfs(1, 0);
printf("%d
", q[id[1]].top());
}
system("pause");
return 0;
}
T4 序列
定义一个数的 se 序列为其一个数位和为 (10) 的子段。
举个例子,1145141919810900 的所有 se 序列为
- 145
- 451
- 514
- 19
- 91
- 19
- 109
- 1090
- 10900
定义一个数是 ll 数,当且仅当它的每一个数位都在至少一个 se 序列中。
举个例子,1145141919810900 不是 ll 数,因为第一个 1 和 8 不在任何一个 se 序列中,而 23541901 是一个 ll 数。
现在牛牛想随机生成一个 ([0,10^n)) 范围内的数送给牛妹。具体地说,每一位上的数字为 (i) 的概率为 (a_i), 且保证 (sumlimits_{i=0}^9 a_i=1)
现在牛牛想知道这个数为 ll 数的概率。
数据范围
对于 (5\%) 的数据,(n=1);
对于 (5\%) 的数据,(n=100);
对于 (20\%) 的数据,(n=3000);
对于另 (30\%) 的数据,(nle 10^{18}),且保证 (forall iin [0,9],a_i=frac{1}{10})
对于 (100\%) 的数据,(1le nle 10^{18}), (forall i in [0,9],0le b_ilt 10^9+7)
solution
正解:特征多项式。
直接弃疗。
std
#include<iostream>
#include<cstdio>
#include<cassert>
#include<cstring>
#include<vector>
#include<map>
using namespace std;
const int N=100005;
const int MOD=1000000007;
int ksm(int a,int b)
{
int res=1;
while(b)
{
if(b&1) res=1LL*res*a%MOD;
a=1LL*a*a%MOD,b>>=1;
}
return res;
}
int getinv(int x)
{
return ksm(x,MOD-2);
}
int n=2816*2;
int cc[10];
int a[N];
vector<int>solve()
{
static vector<int>R[N];
static int c;
static int delta[N];
static int fail[N];
for(int i=1;i<=n;i++)
{
delta[c]=a[i];
for(int j=0;j<(int)R[c].size();j++)
delta[c]=(delta[c]-(long long)a[i-j-1]*R[c][j]%MOD+MOD)%MOD;
if(delta[c]==0) continue;
fail[c]=i;
if(c==0)
{
R[++c]=vector<int>(i,0);
continue;
}
int d=0;
for(int j=1;j<c;j++)
if((int)R[j].size()-fail[j]<(int)R[d].size()-fail[d]) d=j;
int qwq=(long long)delta[c]*getinv(delta[d])%MOD;
R[++c]=vector<int>(i-fail[d]-1,0);
R[c].emplace_back(qwq);
for(auto j:R[d])
R[c].emplace_back((MOD-(long long)qwq*j%MOD)%MOD);
if(R[c].size()<R[c-1].size()) R[c].resize(R[c-1].size());
for(int j=0;j<(int)R[c-1].size();j++)
R[c][j]=(R[c][j]+R[c-1][j])%MOD;
}
return R[c];
}
int k;
long long val[N];
int calc_f(long long n)
{
static int re[N];
static int re_top;
static int x[N];
static int x_top;
static int tp[N];
static int tp_top;
if(n<=k) return a[n];
re[re_top=0]=1;
x[x_top=1]=1;
n-=k;
for(;n;n>>=1)
{
if(n&1)
{
tp_top=re_top;
for(int i=0;i<=re_top+x_top;i++)
tp[i]=re[i],re[i]=0;
re_top=tp_top+x_top;
for(int i=0;i<=tp_top;i++)
for(int j=0;j<=x_top;j++)
re[i+j]=(re[i+j]+(long long)tp[i]*x[j])%MOD;
//times
while(re_top>=k)
{
for(int i=0;i<k;i++)
re[re_top-k+i]=(re[re_top-k+i]+(long long)val[k-i]*re[re_top])%MOD;
re[re_top]=0;
while(re[re_top]==0) re_top--;
}
//mod
}
tp_top=x_top;
for(int i=0;i<=tp_top*2;i++)
tp[i]=x[i],x[i]=0;
x_top=tp_top+tp_top;
for(int i=0;i<=tp_top;i++)
for(int j=0;j<=tp_top;j++)
x[i+j]=(x[i+j]+(long long)tp[i]*tp[j])%MOD;
//times
while(x_top>=k)
{
for(int i=0;i<k;i++)
x[x_top-k+i]=(x[x_top-k+i]+(long long)val[k-i]*x[x_top])%MOD;
x[x_top]=0;
while(x[x_top]==0) x_top--;
}
//mod
}
int ans=0;
for(int i=0;i<=re_top;i++)
ans=(ans+(long long)re[i]*a[k+i])%MOD;
return ans;
}
int tot;
map<pair<long long,int>,int>book;
void dfs(int les,long long val)
{
if(les==0)return;
long long tp=val,q=0;
book[{val,0}]=++tot;
while(tp)
{
q++;
book[{val,q}]=++tot;
tp/=10;
}
for(int i=1;i<=min(les,9);i++)
dfs(les-i,val*10+i);
return;
}
vector<pair<int,int>>tr[2817];
void solve(long long sta,int les)
{
int qwq=book[{sta,les}];
tr[qwq].emplace_back(qwq,cc[0]);
for(int i=1;i<=9;i++)
{
long long tp=sta,sum=0,id=-1;
for(int j=0;j<=8;j++)
{
if(tp%10+sum+i>=10)
{
id=j;
break;
}
sum+=tp%10,tp/=10;
}
if(id==-1)tr[qwq].emplace_back(book[{sta*10+i,les+1}],cc[i]);
else
{
if(id+1<les) continue;
long long nw=1;
for(int j=1;j<=id;j++)
nw*=10;
if(tp%10+sum+i==10) tr[qwq].emplace_back(book[{sta%nw*10+i,0}],cc[i]);
else if(id>=les) tr[qwq].emplace_back(book[{sta%nw*10+i,les+1}],cc[i]);
}
}
return;
}
int main()
{
long long m;
assert(scanf("%lld",&m)==1);
assert(m<=1e18);
static int b[10];
for(int i=0;i<=9;i++)
assert(scanf("%d",&b[i])==1),assert(0<=b[i]&&b[i]<MOD);
long long sum=0;
for(int i=0;i<=9;i++)
sum=(sum+b[i])%MOD;
for(int i=0;i<=9;i++)
cc[i]=(long long)b[i]*getinv(sum)%MOD;
dfs(10,0);
static int ans[2817];
for(auto [vq,id]:book)
if(vq.second==0) ans[id]=1;
for(auto [vq,id]:book)
solve(vq.first,vq.second);
for(int i=1;i<=n;i++)
{
static int tt[2817];
for(int j=1;j<=2816;j++)
for(auto [v,c]:tr[j])
tt[j]=(tt[j]+(long long)ans[v]*c)%MOD;
a[i]=tt[1];
for(int j=1;j<=2816;j++)
ans[j]=tt[j],tt[j]=0;
}
vector<int>ret=solve();
k=ret.size();
for(int i=1;i<=k;i++)
val[i]=ret[i-1];
int Zero=ksm(cc[0],m%(MOD-1));
int res=(calc_f(m)-Zero+MOD)%MOD;
printf("%d",res);
return 0;
}