A 九峰与签到题
题目描述
九峰正在准备一场毒瘤比赛,他是如此毒瘤以致于他想方设法降低通过率,他认为任意时间内通过率大于等于50%的题为签到题,现按照时间顺序给出整场比赛提交记录,请你输出哪些是签到题。
输入描述:
第一行输入两个整数m,n,表示提交记录个数和题目个数
(
m
≤
1
0
5
,
n
≤
20
)
(m\leq 10^5,n\leq20)
(m≤105,n≤20)
接下来m行,每行输入一个正整数
a
i
a_i
ai 和一个字符串op,表示题目序号和评测结果
(
a
i
≤
n
,
o
p
ϵ
{
‘
‘
A
C
"
,
‘
‘
U
N
A
C
"
}
)
(a_i\leq n,op \epsilon \{ ``AC",``UNAC"\})
(ai≤n,opϵ{‘‘AC",‘‘UNAC"})
输出描述:
从小到大输出签到题的序号,按空格分割。若没有则输出-1。
分析:
签到题.
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)1e5;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;
int m, n;
int a[M + 5];
int b[M + 5];
bool vis[M + 5];
void work()
{
scanf("%d %d", &m, &n);
int x; char s[5];
memset(vis, 1, sizeof(vis));
for(int i = 1; i <= m; ++i)
{
scanf("%d %s", &x, s);
if(s[0] == 'A') ++a[x];
++b[x];
if(2 * a[x] < b[x]) vis[x] = 0;
}
bool f = 0;
for(int i = 1; i <= n; ++i)
{
if(vis[i]) printf("%d ", i), f = 1;
}
if(!f) printf("-1");
printf("\n");
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; scanf("%d", &T);
// while(T--) work();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
B 武辰延的字符串
题目描述
众所周知,武辰延很喜欢字符串。
这天,他对着两个字符串 s 和 t 发呆,他发现这两个串的前缀有很多相似的地方,s 的两个前缀连接起来竟也是 t 的前缀。
武辰延想知道有多少对 s 的非空前缀连接起来是 t 的前缀。
形式化地讲,我们把 s i s_i si 看作字符串 s 长度为 i 的前缀。
对于一对前缀 ( s i , s j ) (s_i,s_j) (si,sj) (允许 i=j)而言,当满足 s i + s j = t i + j s_i+s_j=t_{i+j} si+sj=ti+j 时,我们认为这两个 s 的前缀拼接后等于 t 的一个前缀。
两对 s 的前缀 ( s i , s j ) (s_i,s_j) (si,sj) 与 ( s i ′ , s j ′ ) (s_{i'},s_{j'}) (si′,sj′) 不同当且仅当 i ≠ i ′ i\neq i' i=i′ 或 j ≠ j ′ j\neq j' j=j′ 。
输入描述:
第一行一个字符串 s 。
第二行一个字符串 t 。
其中 1 ≤ ∣ s ∣ , ∣ t ∣ ≤ 1 e 5 1 \le \mid s\mid, \mid t\mid \le 1e5 1≤∣s∣,∣t∣≤1e5,只包含小写字母。
输出描述:
输出一行一个整数,表示满足条件的前缀的对数。
分析:
字符串Hash + 二分.
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)1e5;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;
int n, m;
char s[M + 5];
char t[M + 5];
struct Hash
{
int base, mod;
int n, p[M + 5], h[M + 5];
void init(int _base, int _mod, int _n, char* s)
{
base = _base;
mod = _mod;
n = _n;
p[0] = 1, h[0] = 0;
for(int i = 1; i <= n; ++i)
{
p[i] = 1ll * p[i - 1] * base % mod;
h[i] = (1ll * h[i - 1] * base + s[i] - 'a' + 1) % mod;
}
}
int getH(int l, int r)
{
return ((h[r] - 1ll * h[l - 1] * p[r - l + 1]) % mod + mod) % mod;
}
}h[4];
bool check(int i, int mid)
{
int h0 = (1ll * h[0].getH(1, i) * h[0].p[mid] % h[0].mod + h[0].getH(1, mid)) % h[0].mod;
int h1 = (1ll * h[1].getH(1, i) * h[1].p[mid] % h[1].mod + h[1].getH(1, mid)) % h[1].mod;
int h2 = h[2].getH(1, i + mid);
int h3 = h[3].getH(1, i + mid);
return h0 == h2 && h1 == h3;
}
int cal(int i)
{
int l = 0, r = max(0, min(n, m - i)), mid;
while(l < r)
{
mid = (l + r + 1) >> 1;
if(check(i, mid)) l = mid;
else r = mid - 1;
}
return r;
}
void work()
{
scanf("%s", s + 1);
scanf("%s", t + 1);
n = strlen(s + 1), m = strlen(t + 1);
h[0].init(1331, (int)1e9 + 7, n, s); h[1].init(1331, (int)1e9 + 9, n, s);
h[2].init(1331, (int)1e9 + 7, m, t); h[3].init(1331, (int)1e9 + 9, m, t);
ll ans = 0;
for(int i = 1; i <= n; ++i)
{
ans += cal(i);
}
printf("%lld\n", ans);
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; scanf("%d", &T);
// while(T--) work();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
C 九峰与CFOP
大模拟,咕咕咕.
D 温澈滢的狗狗
题目描述
众所周知,温澈滢在宿舍养了一排 n 只狗狗,每只狗狗都有一个颜色 c i c_i ci。同时,它们只喜欢和不同颜色的狗狗玩,否则它们会觉得很单调无趣。
也就是说,如果第 i 只狗狗和第 j 只狗狗颜色不同,那么它们可以拥有亲密关系,它们亲密度可以表示成 ∣ i − j ∣ \mid i-j\mid ∣i−j∣,否则它们就不能建立亲密关系。
当 n 只狗狗颜色两两不同时,它们有 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1) 对亲密关系;当 n 只狗狗的颜色完全一致时,它们之间就不存在任何一对亲密关系。
我们把这么多对亲密关系取出来,以亲密度为第一关键词,编号较小的狗狗的编号为第二关键词,编号较大的狗狗的编号为第三关键词排序(三维都按照升序排序)。
现在温澈滢想知道第 k 对亲密关系是哪一对狗狗,当然,必要的时候你可以告诉温澈滢这些狗狗之间不存在这么多的亲密关系。
输入描述:
第一行输入两个数字 n 和 k 。
第二行给出 n 个数字,第 i 个数字 c i c_i ci 表示第 i 只狗狗的颜色。
其中 1 ≤ n ≤ 1 e 51 ≤ n ≤ 1 e 5 , 1 ≤ k ≤ n ( n − 1 ) 2 1\le n\le 1e51≤n≤1e5 ,1\le k\le \frac{n(n-1)}{2} 1≤n≤1e51≤n≤1e5,1≤k≤2n(n−1)
输出描述:
如果存在第 k 对狗狗,请在一行内输出两只狗狗的编号,小的在前,中间用一个空格隔开。
否则输出 -1 。
分析:
先二分出答案对应的亲密度, c a l ( m i d ) cal(mid) cal(mid) 表示亲密度 ≤ m i d \leq mid ≤mid 的对数,可以尺取求(异色对数 = 所有对数 - 同色对数).
得到答案对应的亲密度之后,再 O ( n ) O(n) O(n) 扫一遍即可.
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)1e5;
const int N = (int)1e2;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)998244353;
int n; ll k;
int c[M + 5];
int cnt[M + 5];
ll cal(int mid)
{
ll ans = 0;
for(int l = 1, r = 0; l <= n; ++l)
{
while(r < min(n, l + mid)) ++cnt[c[++r]];
ans += min(mid, n - l) - cnt[c[l]] + 1;
--cnt[c[l]];
}
return ans;
}
void work()
{
scanf("%d %lld", &n, &k);
for(int i = 1; i <= n; ++i)
{
scanf("%d", &c[i]);
}
int l = 1, r = n, mid;
while(l < r)
{
mid = (l + r) >> 1;
if(cal(mid) >= k) r = mid;
else l = mid + 1;
}
if(r == n)
{
printf("-1\n");
return;
}
k -= cal(r - 1);
for(int i = 1; i + r <= n; ++i)
{
if(c[i] == c[i + r]) continue;
if(!--k)
{
printf("%d %d\n", i, i + r);
return;
}
}
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; cin >> T;
// while(T--) work();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
E 九峰与子序列
题目描述
学会字符串哈希后,动态规划选手九峰想要出一道解法为字符串哈希题,于是wcy给他口胡了一道题,却把九峰难倒了,你能帮他解决这个问题吗?
给定长度为n的字符串序列a和字符串k,询问a有多少子序列拼接起来等于k。
输入描述:
第一行输入一个正整数
n
(
n
≤
40
)
n(n\leq 40)
n(n≤40)和字符串
k
(
∣
k
∣
≤
5
∗
1
0
6
)
k(|k|\leq 5*10^6)
k(∣k∣≤5∗106)
第二行输入n个字符串
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an,表示给定序列
数据保证a中的字符串总长度不超过
5
∗
1
0
6
5*10^6
5∗106 ,输入的所有字符均为小写字母
输出描述:
一行输出一个整数,表示答案
分析:
思路一:
字符串哈希后直接爆搜是 O ( 2 40 ) O(2^{40}) O(240) 的.
考虑使用折半搜索,时间复杂度降到 O ( 2 20 ) O(2^{20}) O(220).
思路二:
设 f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个字符串匹配字符串 s s s 到位置 j j j 的方案数.
则有状态转移方程 f [ i ] [ j ] = f [ i − 1 ] [ j ] + [ t [ i ] = = s [ j − l e n [ i ] : j ] ] f [ i − 1 ] [ j − l e n [ i ] ] f[i][j] = f[i - 1][j] + [t[i] == s[j - len[i]:j]]f[i - 1][j - len[i]] f[i][j]=f[i−1][j]+[t[i]==s[j−len[i]:j]]f[i−1][j−len[i]].
PS:双模数哈希被卡常了,需要用自然溢出.
代码实现:
思路一:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)5e6;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
int n, m;
string k;
string s[40];
int sz[41];
int p[M + 5];
int mod = (int)1e9 + 7;
vector<int> h[41];
void init()
{
sz[n] = k.size();
p[0] = 1;
for(int i = 1; i <= sz[n]; ++i)
{
p[i] = 1ll * p[i - 1] * 1331 % mod;
}
for(int i = 0; i < n; ++i)
{
sz[i] = s[i].size();
h[i].resize(sz[i] + 1);
for(int j = 1; j <= sz[i]; ++j)
{
h[i][j] = (1ll * h[i][j - 1] * 1331 + s[i][j - 1] - 'a' + 1) % mod;
}
}
h[n].resize(sz[n] + 1);
for(int j = 1; j <= sz[n]; ++j)
{
h[n][j] = (1ll * h[n][j - 1] * 1331 + k[j - 1] - 'a' + 1) % mod;
}
}
int getH(int u, int l, int r)
{
return ((h[u][r] - 1ll * h[u][l - 1] * p[r - l + 1]) % mod + mod) % mod;
}
int mp[2][M + 5];
void dfs1(int u, int len, int h0)
{
if(u == m)
{
mp[0][len]++;
return;
}
dfs1(u + 1, len, h0);
if(len + sz[u] <= sz[n])
{
int h2 = (1ll * h0 * p[sz[u]] + getH(u, 1, sz[u])) % mod;
if(h2 == getH(n, 1, len + sz[u]))
dfs1(u + 1, len + sz[u], h2);
}
}
ll quick(ll a, ll b, ll p)
{
ll s = 1;
while(b)
{
if(b & 1) s = s * a % p;
a = a * a % p;
b >>= 1;
}
return s;
}
ll inv(ll n, ll p)
{
return quick(n, p - 2, p);
}
ll ans = 0;
void dfs2(int u, int len, int h0)
{
if(u == m - 1)
{
ans += mp[0][sz[n] - len];
return;
}
dfs2(u - 1, len, h0);
if(len + sz[u] <= sz[n])
{
int h2 = (1ll * getH(u, 1, sz[u]) * p[len] + h0) % mod;
if(h2 == getH(n, sz[n] - (len + sz[u]) + 1, sz[n]))
dfs2(u - 1, len + sz[u], h2);
}
}
void work()
{
cin >> n >> k;
for(int i = 0; i < n; ++i) cin >> s[i];
init();
m = max(0, min(n, n / 2));
dfs1(0, 0, 0);
dfs2(n - 1, 0, 0);
cout << ans << "\n";
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; scanf("%d", &T);
// while(T--) work();
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
思路二:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)5e6;
const int N = (int)3e4;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)998244353;
int n;
char s[M + 5];
ll f[M + 5];
ull p[M + 5];
ull h[M + 5];
inline ull cal(const int& l, const int& r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
void work()
{
scanf("%d %s", &n, s + 1);
int m = strlen(s + 1);
p[0] = 1;
for(int i = 1; i <= m; ++i)
{
p[i] = p[i - 1] * 131;
h[i] = h[i - 1] * 131 + s[i] - 'a' + 1;
}
f[0] = 1;
int sz; ull H;
for(int i = 1; i <= n; ++i)
{
scanf("%s", s + 1); sz = strlen(s + 1);
H = 0; for(int j = 1; j <= sz; ++j) H = H * 131 + s[j] - 'a' + 1;
for(int j = m; j >= sz; --j)
{
if(cal(j - sz + 1, j) == H) f[j] += f[j - sz];
}
}
printf("%lld\n", f[m]);
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; scanf("%d", &T);
// while(T--) work();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
F 魏迟燕的自走棋
题目描述
众所周知,魏迟燕正带领着他的自走棋小队驰骋疆场。
自走棋这个游戏不需要过硬的操作能力,只需要搭配阵容并分配装备即可,非常适合魏迟燕这种手残党。
现在魏迟燕的小队中有 n 个人,仓库中有 m 件装备,每个人只能装备一件装备,每件装备只能分配给一个人。
其中,第 i 件装备可以给 k i k_i ki 个人中的一个,分别为 p 1 , ⋯ , p k i p_1,\cdots,p_{k_i} p1,⋯,pki ,获得的战力提升为 w i w_i wi,总战力提升即为所有士兵战力提升之和。
魏迟燕想知道他能获得的最大总战力提升为多少?
输入描述:
第一行两个整数 n 和 m ,分别表示人数和装备数。
接下来 m 行,其中的第 i 行描述第 i 件装备的信息。
每行第一个整数 k i k_i ki 表示这件装备适用的人数,之后 k i k_i ki 个整数表示每个适用者的编号,最后一个整数 w i w_i wi 表示装备这件装备带来的战力提升。
其中 1 ≤ n , m ≤ 1 e 51 ≤ n , m ≤ 1 e 5 , 1 ≤ k i ≤ 2 1\le n,m\le 1e51≤n,m≤1e5,1\le k_i\le 2 1≤n,m≤1e51≤n,m≤1e5,1≤ki≤2
输出描述:
一行输出一个整数表示能获得的最大战力提升。
分析:
首先对装备按照战力从大到小排序,然后贪心选择装备(能选就选).
由于 k k k 最大只有 2 2 2,所以这样做一定是最优的.
具体实现可以用并查集维护.
对于当前装备的两个人 a a a 和 b b b,则将 a a a 与 b b b 连边加入一个集合.
对于点数为 n n n,边数为 n − 1 n - 1 n−1 的集合,不难证明这 n n n 个人的任何一个人都可以当做未有装备的人.
对于点数为 n n n,边数为 n n n 的集合,则表示此集合已满.
详见代码.
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)1e5;
const int N = (int)1e2;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)998244353;
int n, m;
int fa[M + 5];
bool loop[M + 5];
struct node
{
int k, p[2], w;
bool operator<(const node& b)const
{
return w > b.w;
}
} s[M + 5];
int tofind(int x)
{
if(x == fa[x]) return x;
return fa[x] = tofind(fa[x]);
}
void work()
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= m; ++i)
{
scanf("%d", &s[i].k);
for(int j = 0; j < s[i].k; ++j) scanf("%d", &s[i].p[j]);
scanf("%d", &s[i].w);
}
sort(s + 1, s + m + 1);
for(int i = 1; i <= n; ++i) fa[i] = i, loop[i] = 0;
ll ans = 0;
for(int i = 1, a, b; i <= m; ++i)
{
if(s[i].k == 1)
{
a = tofind(s[i].p[0]);
if(!loop[a])
{
loop[a] = 1;
ans += s[i].w;
}
}
else
{
a = tofind(s[i].p[0]), b = tofind(s[i].p[1]);
if(a != b)
{
if(!loop[a] || !loop[b])
{
fa[a] = b;
ans += s[i].w;
if(loop[a] || loop[b]) loop[b] = 1;
}
}
else if(!loop[a])
{
loop[a] = 1;
ans += s[i].w;
}
}
}
printf("%lld\n", ans);
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; cin >> T;
// while(T--) work();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
G 九峰与蛇形填数
题目描述
蛇形填数是一道经典的入门题,但是九峰有自己的想法,他认为盘着的蛇不是一条好蛇,只有不断前进才能突破自我,变成真龙,因此,相比于将矩阵填数成回型(盘踞的蛇):
1 2 3 8 9 4 7 6 5 \begin{matrix} 1& 2 &3 \\ 8 & 9 &4 \\ 7 & 6 & 5 \end{matrix} 187296345
九峰更喜欢将矩阵填成"S"型(行走的蛇):
1 2 3 6 5 4 7 8 9 \begin{matrix} 1 & 2 & 3 \\ 6 & 5 & 4 \\ 7 & 8 & 9 \end{matrix} 167258349
现在给你一个n*n的初始全零的矩阵,请你将其按第二种方法填数,但是这样子太过简单,所以每一次操作九峰会选择一个子矩阵,请你在其子矩阵上进行填数,并在最后输出整个矩阵
输入描述:
第一行两个整数n,m,表示矩阵的大小和操作次数
(
n
≤
2000
,
m
≤
3000
)
(n\leq 2000,m\leq 3000)
(n≤2000,m≤3000)
接下来m行,每行输入三个正整数x,y,k,表示在以(x,y)为左上角,边长为k的方阵内填数
(
1
≤
x
,
y
≤
n
,
m
a
x
(
x
+
k
−
1
,
y
+
k
−
1
)
≤
n
)
(1\leq x,y \leq n,max(x+k-1,y+k-1)\leq n)
(1≤x,y≤n,max(x+k−1,y+k−1)≤n)
输出描述:
输出一个 n ∗ n n*n n∗n的矩阵,表示最后的结果
分析:
上来写了一发线段树,结果 TLE(事实证明多交几发可以卡过去?
事实上,此题可以 O ( n ( n + m ) ) O(n(n + m)) O(n(n+m)) 解决.
枚举行,再倒序枚举操作,计算列的时候可以用并查集维护已填块.
详见代码.
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)2e3;
const int N = (int)3e3;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)998244353;
int n, m;
int a[M + 5][M + 5];
int x[N + 5], y[N + 5], k[N + 5];
int fa[M + 5];
int ans[M + 5];
int read()
{
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch))
{
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch))
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
void print(int n)
{
if(n < 0)
{
putchar('-');
print(-n);
}
if(n > 9) print(n / 10);
putchar(n % 10 + '0');
}
int tofind(int x)
{
if(x == fa[x]) return x;
return fa[x] = tofind(fa[x]);
}
void work()
{
n = read(); m = read();
for(int i = 1; i <= m; ++i) x[i] = read(), y[i] = read(), k[i] = read();
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= n; ++j) fa[j] = j, ans[j] = 0;
for(int j = m; j >= 1; --j)
{
if(i < x[j] || i > x[j] + k[j] - 1) continue;
for(int l = y[j]; l <= y[j] + k[j] - 1; ++l)
{
if(ans[l])
{
l = tofind(l);
}
else
{
if((i - x[j]) & 1) ans[l] = (i - x[j]) * k[j] + y[j] + k[j] - l;
else ans[l] = (i - x[j]) * k[j] + l - y[j] + 1;
if(l > 1 && ans[l - 1]) fa[l - 1] = tofind(l);
if(l < n && ans[l + 1]) fa[l] = tofind(l + 1);
}
}
}
for(int j = 1; j <= n; ++j) print(ans[j]), putchar(j == n ? '\n' : ' ');
}
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; cin >> T;
// while(T--) work();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
H 吴楚月的表达式
题目描述
众所周知,吴楚月在数据结构课的大作业环节选择了表达式求值。
他觉得实现一个线性的表达式求值太无聊了,于是他把问题丢到了一棵树上。
形式化地讲,这棵树有 n 个节点,1 号点为根,每个节点上都有一个权值 v i v_i vi ,代表参与运算的值。每条边都有一个 o p i op_i opi ,代表运算符。
于是树上一条路径变成了 v − o p − v − ⋯ − v − o p − v v-op-v-\cdots-v-op-v v−op−v−⋯−v−op−v 的形式,对应一个表达式。
吴楚月希望你对树上每一个节点 u ,计算出根到 u 的简单路径所对应的表达式的值。
由于计算结果可能很大,所以你需要对 1e9+7 取模 。
注:表达式优先级即正常的加减乘除的优先级,从左往右,乘除优先级高于加减。
输入描述:
第一行给出一个整数 n 表示节点个数。
第二行 n 个正整数,第 i 个数表示 i 号节点的权值 v i v_i vi 。
第三行 n-1 个正整数,第 i 个数 f a i fa_i fai 表示 i+1 号节点的父亲。
第四行一个长度为 n-1 的字符串, 第 i 个字符表示 i+1 号节点与它父亲之间连边对应的运算符。
其中 1 ≤ n ≤ 1 e 51 ≤ n ≤ 1 e 5 , 1 ≤ v i ≤ 1 e 9 , o p i ∈ { + , − , ∗ , / } 1\le n\le 1e51≤n≤1e5,1\le v_i \le 1e9, op_i\in\{+,-,*,/\} 1≤n≤1e51≤n≤1e5,1≤vi≤1e9,opi∈{+,−,∗,/}.
输出描述:
一行输出 n 个数字,其中第 i 个数字表示根到 i 号节点的路径所对应的表达式的值。
分析
直接模拟就好啦.
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)1e5;
const int N = (int)1e3;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;
int n, cnt;
int w[M + 5];
int head[M + 5];
struct node
{
int v, nx;
} Edge[M * 2 + 5];
char op[M + 5];
ll f[M + 5];
void init()
{
cnt = 0;
for(int i = 1; i <= n; ++i)
{
head[i] = -1;
}
}
void add(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nx = head[u];
head[u] = cnt++;
}
ll quick(ll a, ll b, ll p = mod)
{
ll s = 1;
while(b)
{
if(b & 1) s = s * a % p;
a = a * a % p;
b >>= 1;
}
return s;
}
ll inv(ll n, ll p = mod)
{
return quick(n, p - 2, p);
}
void dfs(int u, ll pre, ll cur)
{
// printf("u = %d\n", u);
for(int i = head[u]; ~i; i = Edge[i].nx)
{
int v = Edge[i].v;
if(op[v] == '+')
{
f[v] = (pre + cur + w[v]) % mod;
dfs(v, (pre + cur) % mod, w[v]);
}
else if(op[v] == '-')
{
f[v] = ((pre + cur - w[v]) % mod + mod) % mod;
dfs(v, (pre + cur) % mod, (-w[v] % mod + mod) % mod);
}
else if(op[v] == '*')
{
f[v] = (pre + cur * w[v]) % mod;
dfs(v, pre, cur * w[v] % mod);
}
else if(op[v] == '/')
{
f[v] = (pre + cur * inv(w[v])) % mod;
dfs(v, pre, cur * inv(w[v]) % mod);
}
}
}
void work()
{
scanf("%d", &n); init();
for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);
for(int i = 2, fa; i <= n; ++i)
{
scanf("%d", &fa);
add(fa, i);
}
scanf("%s", op + 2);
f[1] = w[1];
dfs(1, 0, w[1]);
for(int i = 1; i <= n; ++i) printf("%lld%c", f[i], i == n ? '\n' : ' ');
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; scanf("%d", &T);
// while(T--) work();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
I 九峰与分割序列
题目描述
给出一个序列,将其分割成若干个子区间,子区间的贡献为:若前一个子区间的长度大于k且该区间长度小
于等于k,则贡献为区间和的两倍,否则贡献为区间和,求一种分割方法,使得所有子区间贡献之和最大,输出最大贡献。
输入描述:
第一行输入两个正整数n和k
(
k
≤
n
≤
1
0
5
)
(k\leq n\leq 10^5)
(k≤n≤105)
第二行输入n个正整数
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an (对于所有
1
≤
i
≤
n
,
a
i
≤
1
0
9
1\leq i \leq n,a_i\leq 10^9
1≤i≤n,ai≤109)
输出描述:
输出一个整数,表示最大贡献和
分析:
很明显这题 dp
设 f [ i ] [ 0 ] f[i][0] f[i][0] 表示考虑了前 i i i 个数,且第 i i i 个数所属子区间的长度小于等于 k k k.
\quad f [ i ] [ 1 ] f[i][1] f[i][1] 表示考虑了前 i i i 个数,且第 i i i 个数所属子区间的长度大于 k k k.
则有状态转移方程:
f [ i ] [ 0 ] = m a x { f [ j ] [ 0 ] + s [ i ] − s [ j ] m a x ( 0 , i − k ) ≤ j ≤ i − 1 f [ j ] [ 1 ] + 2 s [ i ] − 2 s [ j ] m a x ( 0 , i − k ) ≤ j ≤ i − 1 f[i][0] = max \left\{ \begin{matrix} f[j][0] + s[i] - s[j] \;\;\; \qquad max(0, i - k) \leq j \leq i - 1 \\ f[j][1] + 2s[i] - 2s[j] \qquad max(0, i - k) \leq j \leq i - 1 \end{matrix} \right. f[i][0]=max{f[j][0]+s[i]−s[j]max(0,i−k)≤j≤i−1f[j][1]+2s[i]−2s[j]max(0,i−k)≤j≤i−1
f [ i ] [ 1 ] = m a x { f [ j ] [ 0 ] + s [ i ] − s [ j ] 0 ≤ j ≤ i − k − 1 f [ j ] [ 1 ] + s [ i ] − s [ j ] 0 ≤ j ≤ i − k − 1 f[i][1] = max \left\{ \begin{matrix} f[j][0] + s[i] - s[j] \qquad 0 \leq j \leq i - k - 1 \\ f[j][1] + s[i] - s[j] \qquad 0 \leq j \leq i - k - 1 \end{matrix} \right. f[i][1]=max{f[j][0]+s[i]−s[j]0≤j≤i−k−1f[j][1]+s[i]−s[j]0≤j≤i−k−1
用 3 3 3 颗线段树分别维护 f [ j ] [ 0 ] − s [ j ] , f [ j ] [ 1 ] − 2 s [ j ] , f [ j ] [ 1 ] − s [ j ] f[j][0] - s[j], f[j][1] - 2s[j], f[j][1] - s[j] f[j][0]−s[j],f[j][1]−2s[j],f[j][1]−s[j] 的区间最大值即可.
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)1e5;
const int N = (int)1e3;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;
int n, k;
ll s[M + 5];
ll f[M + 5][2];
struct node
{
ll mx;
} tree[3][M * 4 + 5];
inline int lc(int k) {return k<<1;}
inline int rc(int k) {return k<<1|1;}
void push_up(int p, int k)
{
tree[p][k].mx = max(tree[p][lc(k)].mx, tree[p][rc(k)].mx);
}
void build(int p, int k, int l, int r)
{
if(l == r)
{
tree[p][k].mx = -inf;
return;
}
int mid = (l + r) >> 1;
build(p, lc(k), l, mid);
build(p, rc(k), mid + 1, r);
push_up(p, k);
}
void update(int p, int k, int l, int r, int a, ll b)
{
if(l == r)
{
tree[p][k].mx = b;
return;
}
int mid = (l + r) >> 1;
if(a <= mid) update(p, lc(k), l, mid, a, b);
else update(p, rc(k), mid + 1, r, a, b);
push_up(p, k);
}
ll query(int p, int k, int l, int r, int a, int b)
{
if(l >= a && r <= b) return tree[p][k].mx;
int mid = (l + r) >> 1; ll mx = -inf;
if(a <= mid) mx = max(mx, query(p, lc(k), l, mid, a, b));
if(mid < b) mx = max(mx, query(p, rc(k), mid + 1, r, a, b));
return mx;
}
void work()
{
scanf("%d %d", &n, &k);
for(int i = 1; i <= n; ++i) scanf("%lld", &s[i]), s[i] += s[i - 1];
build(0, 1, 1, n); build(1, 1, 1, n); build(2, 1, 1, n);
memset(f, -inf, sizeof(f));
f[1][0] = s[1]; update(0, 1, 1, n, 1, f[1][0] - s[1]);
for(int i = 2; i <= n; ++i)
{
f[i][0] = max(f[i][0], s[i] + query(0, 1, 1, n, max(1, i - k), i - 1));
f[i][0] = max(f[i][0], 2 * s[i] + query(2, 1, 1, n, max(1, i - k), i - 1));
if(i == k) f[i][0] = max(f[i][0], s[i]);
if(i - k - 1 >= 1) f[i][1] = max(f[i][1], s[i] +
max(query(0, 1, 1, n, 1, i - k - 1),
query(1, 1, 1, n, 1, i - k - 1)));
if(i == k + 1) f[i][1] = max(f[i][1], s[i]);
// printf("f[%d][0] = %lld\tf[%d][1] = %lld\n", i, f[i][0], i, f[i][1]);
update(0, 1, 1, n, i, f[i][0] - s[i]);
update(1, 1, 1, n, i, f[i][1] - s[i]);
update(2, 1, 1, n, i, f[i][1] - 2 * s[i]);
}
printf("%lld\n", max(f[n][0], f[n][1]));
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; scanf("%d", &T);
// while(T--) work();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}
J 邬澄瑶的公约数
题目描述
众所周知,邬澄瑶正在学习欧几里得算法。
现在她已经可以轻松求解 gcd ( x 1 , ⋯ , x n ) \gcd(x_1,\cdots,x_n) gcd(x1,⋯,xn),并为此洋洋得意。为了整治狂妄自大的邬澄瑶,她的室友把 gcd ( x 1 p 1 , ⋯ , x n p n ) \gcd(x_1^{p_1},\cdots,x_n^{p_n}) gcd(x1p1,⋯,xnpn) 这个式子甩给了他。
邬澄瑶被难住了,只好来求助于你,希望你帮她求出这个式子。
由于结果可能很大,你需要对 1e9+7 取模。
特别地,邬澄瑶的室友认为 gcd ( x ) = x \gcd(x) = x gcd(x)=x。
输入描述:
第一行一个数表示 n 。
第二行 n 个数,第 i 个数表示 x i x_i xi 。
第三行 n 个数,第 i 个数表示 p i p_i pi 。
其中, 1 ≤ n , x i , p i ≤ 1 e 4 1\le n,x_i,p_i\le1e4 1≤n,xi,pi≤1e4。
输出描述:
输出一行一个数表示答案。
分析:
把每个 x i x_i xi 分解质因数,乘 p i p_i pi 再取 min 即可.
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
const int M = (int)1e5;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;
bool is_prime[M + 5];
int prime[M + 5], cnt;
void init()
{
memset(is_prime, 1, sizeof(is_prime));
is_prime[0] = is_prime[1] = 0;
for(int i = 2; i <= M; ++i)
{
if(is_prime[i])
{
prime[++cnt] = i;
}
for(int j = 1; j <= cnt && i * prime[j] <= M; ++j)
{
is_prime[i * prime[j]] = 0;
if(i % prime[j] == 0)
{
break;
}
}
}
}
int n;
int x[M + 5];
int p[M + 5];
int mp[M + 5];
int v[M + 5];
void divide(int x, int p)
{
for(int i = 1; i <= cnt; ++i) v[i] = 0;
for(int i = 1; i <= cnt && 1ll * prime[i] * prime[i] <= x; ++i)
{
if(x % prime[i] == 0)
{
int c = 0;
while(x % prime[i] == 0)
{
++c;
x /= prime[i];
}
v[i] = c * p;
}
}
if(x > 1) v[lower_bound(prime + 1, prime + cnt + 1, x) - prime] = p;
for(int i = 1; i <= cnt; ++i) mp[i] = min(mp[i], v[i]);
}
ll quick(ll a, ll b, ll p = mod)
{
ll s = 1;
while(b)
{
if(b & 1) s = s * a % p;
a = a * a % p;
b >>= 1;
}
return s;
}
void work()
{
memset(mp, inf, sizeof(mp));
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &x[i]);
for(int i = 1; i <= n; ++i) scanf("%d", &p[i]);
for(int i = 1; i <= n; ++i) divide(x[i], p[i]);
ll ans = 1;
for(int i = 1; i <= cnt; ++i) ans = ans * quick(prime[i], mp[i]) % mod;
printf("%lld\n", ans);
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
// int T; scanf("%d", &T);
// while(T--) work();
init();
work();
// cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
return 0;
}