前置知识点:无
题意:问本质不同的回文子串个数最少的长度为n的字符串有多少种,答案对1e9+7取模
解题思路:其实是一个思维题,先考虑n很大的时候,如果是如abcabc…这样排列,本质不同的回文子串个数就是3,稍微画一画会当n>=4的时候本质不同回文子串个数不可能小于3个,而如果是3个就最多有3个字母. 当是3个字母的时候,肯定不可以有相邻的相同字母,也不能有一个位置左右两边是同一个字母,那么就只能是abcabc……这种形式了。所以n>=4的时候答案是26 * 25 * 24,而n <= 3的时候手画一下会发现答案是26^n
#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
int main()
{
int T; cin>>T;
while(T--){
int n; scanf("%d", &n);
if(n == 1) printf("26\n");
else if(n == 2) printf("%d\n", 26*26);
else if(n == 3) printf("%d\n", 26*26*26);
else printf("%d\n", 26*25*24);
}
}
前置知识点:快速幂,逆元,斐波那契通项公式
题意:给出n,c,k,求 ∑ i = 0 n F ( i ∗ c ) k \sum_{i=0}^nF(i*c)^k ∑i=0nF(i∗c)k
其中 F ( i ) F(i) F(i)表示斐波那契数列第i项。
解题思路:我们知道 F ( n ) = 1 5 ( ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ) F(n)=\frac{1}{\sqrt 5}((\frac{1+\sqrt 5}{2})^n-(\frac{1-\sqrt 5}{2})^n) F(n)=51((21+5)n−(21−5)n)
可以写成 F ( n ) = d ( A n − B n ) F(n)=d(A^n-B^n) F(n)=d(An−Bn)
那么 F ( n ) k = ∑ i = 0 k ( − 1 ) i C ( k , i ) A n ( k − i ) B n i F(n)^k=\sum_{i=0}^k(-1)^iC(k,i)A^{n(k-i)}B^{ni} F(n)k=∑i=0k(−1)iC(k,i)An(k−i)Bni
那么 ∑ i = 0 n F ( i ∗ c ) k = ∑ i = 0 k ( − 1 ) i C ( k , i ) ∗ ( ∑ j = 0 n A ( k − i ) ∗ j c B i j c ) \sum_{i=0}^nF(i*c)^k=\sum_{i=0}^k(-1)^iC(k,i)*(\sum_{j=0}^nA^{(k-i)*jc}B^{ijc}) ∑i=0nF(i∗c)k=∑i=0k(−1)iC(k,i)∗(∑j=0nA(k−i)∗jcBijc)
后面的那段求和是一段等比数列,运用求和公式就能得到答案。
踩坑:注意特判公比为1的时候……
#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int mod = 1000000009;
const int sqrt5 = 383008016;
const int inv2 = (mod+1)/2;
int qm(int a, int b){
ll res = 1;
while(b){
if(b&1) res = (ll)res*a%mod;
a = (ll)a*a%mod;
b >>= 1;
} return res;
}
const int maxn = 2e5 + 50;
int fac[maxn], ifac[maxn];
int Com(int n, int k){
assert(k <= n);
return (ll)fac[n]*ifac[k]%mod*ifac[n-k]%mod;
}
int pa[maxn], pb[maxn];
int main()
{
fac[0] = ifac[0] = 1;
fors(i, 1, maxn) fac[i] = (ll)fac[i-1]*i%mod, ifac[i] = qm(fac[i], mod-2);
int A = (ll)(1+sqrt5)*inv2%mod;
int B = (ll)(1-sqrt5+mod)*inv2%mod;
int inv_sqrt5 = qm(sqrt5, mod-2);
int T; cin>>T;
while(T--){
ll n, c;
int k;
scanf("%lld%lld%d", &n, &c, &k);
//n = 1e18; c = 1e18; k = 1e5 ;
int ans = 0;
int ac = qm(A, c%(mod-1));
//cout<<"A:"<
int bc = qm(B, c%(mod-1));
pa[0] = pb[0] = 1;
fors(i, 1, k+1){
pa[i] = (ll)pa[i-1]*ac%mod;
pb[i] = (ll)pb[i-1]*bc%mod;
}
fors(i, 0, k+1){
//cout<<"i:"<
int q = (ll)pa[k-i]*pb[i]%mod;
int t;
if(q == 1) t = n%mod;
else t = ((ll)q*(qm(q, n%(mod-1))-1) % mod) * qm(q-1, mod-2)%mod;
int flag = 1;
if(i&1) flag = -1;
ans = (ans + (ll)flag*t%mod*Com(k, i)%mod)%mod;
}
ans = (ll)ans*qm(inv_sqrt5, k)%mod;
ans = (ans + mod)%mod;
//printf("%d\n", ans);
}
}
/*
2
1 1 8
1000000000000000000 1000000000000000000 100000
*/
前置知识点:线段树or树状数组
题意:给n点m边的图,每个点 i i i有一个权值 a i a_i ai。 s i s_i si代表和点 i i i相邻的点的权值的集合。
两种操作:
1 u x修改点u的权值为x
2 u,查询 s u s_u su的MEX。
MEX是集合中最小的没出现过的非负数。
解题思路:当时赛场上用奇怪的暴力莽过去了,这里还是介绍题解的方法吧
思路是给点分为两类:度数小于等于 n \sqrt n n的分为A类,其他点分为B类。
当修改点的权值的时候,如果这个点是A类点,那么在所有和它相邻的点里删掉它原本的权值,加入它的新权值。删除/添加操作次数不会超过 s q r t ( n ) sqrt(n) sqrt(n)
当查询点的权值的时候,这个点已经有了和它相邻的所有A类点的信息,但是还缺少相邻的B类点的信息,那么就访问和它相邻的B类点去获得信息, B B B类点不会超过 n \sqrt n n个。
可以对每个点建立一个动态开点的线段树去查询答案and修改信息。每次操作复杂度 l o g ( 度 数 ) log(度数) log(度数)
总复杂度 O ( n n log n ) O(n\sqrt n \log n) O(nnlogn)
(这种复杂度居然能过1e5数据……
#include
#define ll long long
#define pb push_back
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
#define mid ((l+r)>>1)
using namespace std;
const int maxn = 1e5 + 50;
int a[maxn*100], lc[maxn*100], rc[maxn*100], tot;
int B = 333;
int rt[maxn];
vector<int> g[maxn], cnt[maxn], adj[maxn];
int s[maxn];
int n, m;
void init(){
scanf("%d%d", &n, &m);
fors(i, 0, n+1) g[i].clear(), cnt[i].clear(), adj[i].clear();
fors(i, 0, n+1) rt[i] = 0;
tot = 0;
fors(i, 1, n+1) scanf("%d", &s[i]);
while(m--){
int u, v;
scanf("%d%d", &u, &v);
g[u].pb(v); g[v].pb(u);
}
fors(i,1,n+1) {
sort(g[i].begin(), g[i].end());
g[i].erase(unique(g[i].begin(), g[i].end()), g[i].end());
cnt[i].resize(g[i].size()+1, 0);
}
fors(i, 1, n+1){
for(int v: g[i]) if(g[v].size() > B) adj[i].pb(v);
}
}
void update(int &p, int l, int r, int pos, int x){
if(!p){p = ++tot; a[p] = lc[p] = rc[p] = 0;}
if(l == r){a[p] += x; return;}
if(pos <= mid) update(lc[p], l, mid, pos, x);
else update(rc[p], mid+1, r, pos, x);
a[p] = a[lc[p]]+a[rc[p]];
}
int qry(int p, int l, int r){
//cout<<"p:"<
if(l == r || p == 0) return l;
if(a[lc[p]] < (mid-l+1)) return qry(lc[p], l, mid);
else return qry(rc[p], mid+1, r);
}
void add(int u, int x){//
if(cnt[u].size() <= x) return;
if(!cnt[u][x]) update(rt[u], 0, cnt[u].size()-1, x, 1);
cnt[u][x]++;
}
void del(int u, int x){
if(cnt[u].size() <= x) return;
--cnt[u][x];
if(!cnt[u][x]) update(rt[u], 0, cnt[u].size()-1, x, -1);
}
void sol(){
int q; scanf("%d", &q);
fors(i, 1, n+1){
if(g[i].size() <= B) for(int v:g[i]) add(v, s[i]);
}
while(q--){
int op; scanf("%d", &op);
if(op == 1){
int u, x;scanf("%d%d", &u, &x);
if(g[u].size() <= B){
for(int v: g[u]){
del(v, s[u]); add(v, x);
}
}
s[u] = x;
}else{
int u; scanf("%d", &u);
for(int v: adj[u]){
add(u, s[v]);
}
printf("%d\n", qry(rt[u], 0, cnt[u].size()-1));
for(int v: adj[u]){
del(u, s[v]);
}
}
}
}
int main()
{
int T; cin>>T;
while(T--){
init();
sol();
}
}
下面是因为数据水ac的假算法:
#include
#define ll long long
#define pb push_back
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 1e5 + 50;
vector<int> g[maxn], cnt[maxn];
int a[maxn], mex[maxn];
int n, m;
bool cmp(int x, int y){return g[x].size() < g[y].size();}
void del(int u, int x){
cnt[u][x]--;
if(cnt[u][x] == 0) mex[u] = min(mex[u], x);
return;
}
void add(int u, int x){
cnt[u][x]++;
int now = mex[u];
while(cnt[u][now] != 0) {
now++;
assert(now < cnt[u].size());
}
mex[u] = now;
return;
}
void init(){
scanf("%d%d", &n, &m);
fors(i, 0, n+1) g[i].clear(), cnt[i].clear(), mex[i] = 0;
fors(i, 1, n+1) scanf("%d", &a[i]);
while(m--){
int u, v;
scanf("%d%d", &u, &v);
g[u].pb(v); g[v].pb(u);
}
fors(i,1,n+1) {
sort(g[i].begin(), g[i].end());
g[i].erase(unique(g[i].begin(), g[i].end()), g[i].end());
}
fors(i,1,n+1){
sort(g[i].begin(), g[i].end(), cmp);
cnt[i].resize(g[i].size()+1, 0);
}
}
void sol(){
fors(i, 1, n+1){
if(g[i].size() == 0) continue;
for(int j = g[i].size()-1; j >= 0; j--){
int v = g[i][j];
if(g[v].size() <= a[i]) break;
add(v, a[i]);
}
}
int q; scanf("%d", &q);
while(q--){
int op; scanf("%d", &op);
if(op == 1){
int u, x; scanf("%d%d", &u, &x);
if(g[u].size() == 0){a[u] = x; continue;}
for(int j = g[u].size()-1; j >= 0; --j){
int v = g[u][j];
if(g[v].size() <= a[u]) break;
del(v, a[u]);
}
for(int j = g[u].size()-1; j >= 0; --j){
int v = g[u][j];
if(g[v].size() <= x) break;
add(v, x);
}
a[u] = x;
}
else {
int u; scanf("%d", &u);
printf("%d\n", mex[u]);
}
}
}
int main()
{
int T; cin>>T;
while(T--){
init();
sol();
}
}
前置知识点:无
题意:给出n个人的初始位置和加速度,运动方向都是向右,求有多少人可以作为leader。当某时刻一个人是唯一一个最领先的人,那么在那个独属的moment, 它就是leader~
解题思路:
初速度 p i p_i pi,加速度 a i a_i ai,那么路程 x i = p i + 1 / 2 a i ∗ t 2 x_i=p_i+1/2 a_i*t^2 xi=pi+1/2ai∗t2,那每个人可以看成y关于x的函数: y = b + a ∗ x y = b+a*x y=b+a∗x,其中x和时间正相关
那么对于两个人i和j,i的加速度大于j,什么时候i跑到j的前面去?
b i + a i x ≥ b j + a j x b_i+a_ix \ge b_j+a_jx bi+aix≥bj+ajx
也就是 x ≤ b j − b i a i − a j x \le \frac{b_j-b_i}{a_i-a_j} x≤ai−ajbj−bi。更对称点,写成 x ≤ ( − b i ) − ( − b j ) a i − a j x \le \frac{(-b_i)-(-b_j)}{a_i-a_j} x≤ai−aj(−bi)−(−bj)
后面那个式子就是 ( a i , − b i ) , ( a j , − b j ) (a_i,-b_i),(a_j,-b_j) (ai,−bi),(aj,−bj)组成的直线的斜率,斜率的值代表i超过j的时间。
那么我们可以把所有机器人抽象成 ( x i , y i ) (x_i,y_i) (xi,yi)的点, x i = a i , y i = − 2 p i x_i=a_i, y_i = -2p_i xi=ai,yi=−2pi
这些点按照加速度排序。然后维护一个斜率单调上升的点的队列。两点之间的斜率代表后面的点超过前面的点的时间。考虑队列最后面是j,倒数第二个是k,新加入一个点i,如果j->i的斜率小于k->j的斜率,说明i在j超过k之前就超过了j,此时j不可能作为leader了,从队列中弹出,继续用队列中最后两个点判断直到队列中的点数少于两个或者满足j->i的斜率大于k->j的斜率。
最后队列中剩下的点就是可以作为leader的点。
一些细节:
j->i斜率为负数的情况表示i一开始就超过j了,直接把j弹出去。
如果加速度相等,只保留初始位置最大的点。注意去重。
#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
#define P pair
using namespace std;
const int maxn = 1e5 + 50;
P a[maxn];
int q[maxn], tot = 0;
int same[maxn];
int main()
{
int T; cin>>T;
while(T--){
int n; scanf("%d", &n);
fors(i, 0, n) scanf("%lld%lld", &a[i].second, &a[i].first), a[i].second *= -2, same[i] = 0;
sort(a, a+n);
fors(i, 1, n)
if(a[i].first == a[i-1].first && a[i].second == a[i-1].second) same[i] = same[i-1] = 1;
tot = 0;
q[tot++] = 0;
fors(i, 1, n){
int pre = q[tot-1];
if(a[i].first == a[pre].first) continue;//加速度相等,则bi >= b_{i-1},useless
while(tot > 0 && a[i].second <= a[q[tot-1]].second) tot--;//b[i]也是递增的
while(tot > 1){
int j = q[tot-1], k = q[tot-2];
if( (a[i].second-a[j].second)*(a[j].first-a[k].first) <= (a[j].second-a[k].second)*(a[i].first-a[j].first) )
tot--;
else break;
}
q[tot++] = i;
}
int ans = 0;
fors(i, 0, tot) if(!same[q[i]]) ans++;
printf("%d\n", ans);
}
}
前置知识点:Lyndon串
题意:给一个长为n的串S,对于它的每个前缀,求字典序最小的后缀的起始下标。
解题思路:思考这个题。思考完之后学一下Lyndon分解算法
Lyndon分解算法在这道题的应用:在求Lyndon分解的过程中,i从新的v开始的时候意味着一个新的开头,这个v的字典序比前面所有后缀都要小,那么v的开端对应的答案是i。
然后考虑加入s[k]的过程
s[k] = s[j]的时候,是一个继续周期循环的过程,那么它的答案可以沿用前面的信息:ans[k] = ans[j]+k-j。
这种情况不可以直接算出v的起始位置作为答案:考虑ababbababb,在位置9也就是倒数第二个的位置,此时v是abab,循环节是ababb,而循环节内部其实在之前经历了更小的循环节的求解步骤,只是因为后来加了一个大的字符而融合了,那么这之后再循环如果直接算新循环的起始位置就会丢失这个循环内部可能的小循环的信息,沿用上一循环的信息可以保证这些信息一直延续下来。
s[k] > s[j]的时候,会把前面的循环都融合,此时整个从i到k的串是Lyndon串,所以这个时候ans[k]=i
s[k],显然v+s[k]是最小的后缀。不过这个时候可以不急统计,反正会从i=v的开端重新开始算法。在重新开始的时候会设置i=v的开头, ans[i] = i。
#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 1e6 + 50;
ll q[maxn], sed = 1112;
const int mod = 1e9 + 7;
char s[maxn];
int ans[maxn];
int tmp[maxn], cnt = 0;
int main()
{
q[0] = 1;
fors(i, 1, maxn) q[i] = (ll)q[i-1]*sed%mod;
int T; cin>>T; //T = 100;
int ca = 0;
while(T--){
scanf("%s", s+1);
int n = strlen(s+1);
s[++n] = '\0';
//ans[1] = 1;
int i = 1;
while(i <= n){
ans[i] = i;
int j = i, k = i+1;
while(k <= n && s[j] <= s[k]){
//cout<<"s[k]:"<
//cout<<"k:"<
if(s[j] < s[k]) j = i, ans[k] = i;
else {
//cout<<"k:"<
int t = k-j;
ans[k] = ans[j]+t;
j++;
}
//cout<<"k:"<
k++;
}
//cout<<"k:"<
while(i <= j){
//i+k-j-1为分割点
//cout<<"p:"<
i += k-j;
}
}
ll Ans = 0;
fors(i, 1, n) Ans = (Ans + (ll)ans[i]*q[i-1])%mod;
printf("%lld\n", Ans);
}
}
/*
5
ababbababba
*/