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\neq b\) 的位置個數對 \(2\) 取模。
\(n, m, q \leq 2\times 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\n");
else printf("1\n");
}
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<<"\n";
}
puts("");
return 0;
}
T2 方格計數
題目描述
在左下角是 \((0,0)\),右上角是 \((W,H)\) 網格上,有 \((W+1)\times (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)\times (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\n", (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\n", 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 \neq 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\n", 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\), 且保證 \(\sum\limits_{i=0}^9 a_i=1\)
現在牛牛想知道這個數為 ll 數的概率。
數據范圍
對於 \(5\%\) 的數據,\(n=1\);
對於 \(5\%\) 的數據,\(n=100\);
對於 \(20\%\) 的數據,\(n=3000\);
對於另 \(30\%\) 的數據,\(n\le 10^{18}\),且保證 \(\forall i\in [0,9],a_i=\frac{1}{10}\)
對於 \(100\%\) 的數據,\(1\le n\le 10^{18}\), \(\forall i \in [0,9],0\le b_i\lt 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;
}
