php基礎-14
A-All with Pairs
題面
樣例
輸入
3
ab
ba
aba
輸出
29
說明
題意
給出n個字串,求每個字串和其他字串(包括自己)的字首和字尾相同的最大的長度,答案為所有長度的平方和。
題解
如果單純求出所有的字首和字尾相同的長度平方和,那麼我們可以列舉字首,利用hash儲存所有的字尾,每次看看有多少相同的,統計答案即可。
但是由於我們要的是最大的長度,所以有一些是計算重複的,比如兩個字串“aba“,”aba”,會有兩個字首a和aba被算到,實際上a不是最長的,是不應計算的。所以我們要去重。
去重的方法就是利用kmp的nxt陣列,nxt陣列的含義是:nxt[i]表示最大的x,滿足s[1 : x] 是s[1 : i] 的字尾,這樣我們將cnt[nxt[j]]-=cnt[j]
程式碼
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e6 + 50; const int M = 1e5 + 50; const int mod = 998244353; const int base = 233; char s[N]; vector<long long> h[M]; vector<int> nxt[M]; ll p[N]; int cnt[N]; void getNxt(int id) { int m = strlen(s + 1); nxt[id].resize(m + 1); int j = 0; for (int i = 2; i <= m; i++) { while (j && s[i] != s[j + 1]) j = nxt[id][j]; if (s[i] == s[j + 1]) j++; nxt[id][i] = j; } } int main() { p[0] = 1; for (int i = 1; i < N; i++) p[i] = p[i - 1] * base; map<ll, int> mp; int n; scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%s", s + 1); int len = strlen(s + 1); h[i].resize(len + 1); h[i][0] = 0; for (int j = 1; j <= len; j++) h[i][j] = h[i][j - 1] * base + s[j] - 'a' + 1; for (int j = 0; j < len; j++) mp[h[i][len] - h[i][j] * p[len - j]]++; getNxt(i); } int ans = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j < h[i].size(); j++) { cnt[j] = mp[h[i][j]]; cnt[nxt[i][j]] -= cnt[j]; } for (int j = 1; j < h[i].size(); j++) ans = (ans + 1ll * cnt[j] * j % mod * j % mod) % mod; } printf("%d\n", ans); return 0; }
B-Boundary
題面
樣例
輸入
4
1 1
0 2
2 0
2 2
輸出
3
題意
給出n個點,每個圓都過(0,0),找出邊界上點最多的圓,輸出最多的點數
題解
3點確定一個圓,列舉兩個點,我們可以用兩條不共線的弦中垂線的交點求出圓心,如果一個圓上含有x個點,那麼有\(C(x,2)=x*(x-1)/2\)次列舉時都會求出這個圓心,所以求出最多的圓心出現次數,也就求出了答案
兩條中垂線分別為
\[y-\frac{y_i}2=-\frac{x_i}{y_i}(x-\frac{x_i}2)\\ y-\frac{y_j}2=-\frac{x_j}{y_j}(x-\frac{x_j}2) \]
即
\[2x_ix+2y_iy=x_i^2+y_i^2\\ 2x_jx+2y_jy=x_j^2+y_j^2 \]
令
\[a11=2x_i,a12=2y_i\\ a21=2x_j,a22=2y_j\\ a13=x_i^2+y_i^2\\ a14=x_j^2+y_j^2 \]
使用克萊姆法則求方程組的解即可
程式碼
#include <bits/stdc++.h>
#define pdd pair<double, double>
using namespace std;
typedef long long ll;
struct READ {
inline char read() {
#ifdef _WIN32
return getchar();
#endif
static const int IN_LEN = 1 << 18 | 1;
static char buf[IN_LEN], *s, *t;
return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
}
template <typename _Tp> inline READ & operator >> (_Tp&x) {
static char c11, boo;
for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
if(c11 == -1) return *this;
boo |= c11 == '-';
}
for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
boo && (x = -x);
return *this;
}
} in;
const int N = 2e5 + 50;
const double eps = 1e-8;
int x[N], y[N];
template <typename T> T cross(T a, T b, T c, T d) {
return a * d - b * c;
}
bool check(pdd a, pdd b) {
if (fabs(a.first - b.first) > eps) return false;
if (fabs(a.second - b.second) > eps) return false;
return true;
}
int main() {
int n; in >> n;
for (int i = 1; i <= n; i++) in >> x[i] >> y[i];
int mx = 0;
vector<pdd> s;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (x[i] * y[j] - x[j] * y[i] == 0) continue;
ll a11 = 2 * x[i], a12 = 2 * y[i], a13 = x[i] * x[i] + y[i] * y[i];
ll a21 = 2 * x[j], a22 = 2 * y[j], a23 = x[j] * x[j] + y[j] * y[j];
ll d0 = cross(a11, a12, a21, a22);
ll d1 = cross(a13, a12, a23, a22);
ll d2 = cross(a11, a13, a21, a23);
s.push_back(pdd((double)d1 / d0, (double)d2 / d0));
}
}
sort(s.begin(), s.end());
int now = 1;
for (int i = 1; i < s.size(); i++) {
if (check(s[i], s[i - 1])) now++;
else now = 1;
mx = max(mx, now);
}
for (int i = 1; i <= 2000; i++) {
if ((i - 1) * i / 2 == mx) {
printf("%d\n", i);
break;
}
}
return 0;
}
C-Cover the Tree
題面
樣例
輸入
5
1 2
1 3
2 4
2 5
輸出
2
2 3
4 5
說明
題意
給一棵樹,求最少的鏈覆蓋所有的邊,輸出最少的鏈的數目和方案
題解
程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
inline char read() {
#ifdef _WIN32
return getchar();
#endif
static const int IN_LEN = 1 << 18 | 1;
static char buf[IN_LEN], *s, *t;
return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
}
template <typename _Tp> inline READ & operator >> (_Tp&x) {
static char c11, boo;
for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
if(c11 == -1) return *this;
boo |= c11 == '-';
}
for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
boo && (x = -x);
return *this;
}
} in;
const int N = 2e5 + 50;
int ind[N];
vector<int> G[N];
vector<int> s;
void dfs(int u, int f) {
if (ind[u] == 1) s.push_back(u);
for (auto v : G[u]) {
if (v != f) dfs(v, u);
}
}
int main() {
int n; in >> n;
for (int i = 1; i < n; i++) {
int u, v;
in >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
ind[u]++; ind[v]++;
}
int rt = 1;
for (int i = 1; i <= n; i++) {
if (ind[i] > 1) rt = i;
}
dfs(rt, 0);
int m = s.size();
printf("%d\n", (m + 1) / 2);
for (int i = 1; i * 2 <= m; i++) printf("%d %d\n", s[i - 1], s[i - 1 + (m + 1) / 2]);
if (m & 1) printf("%d %d\n", rt, s[(m + 1) / 2 - 1]);
return 0;
}
D-Duration
簽到題
E-Exclusive Or
等待填坑,這種題不適合我
F-Fake Maxpooling
題面
樣例
輸入
3 4 2
輸出
38
說明
題意
給出一個n*m的矩陣,矩陣中的值\(a[i][j]=lcm(i,j)\),求所有k*k子矩陣的最大值和
題解
可以使用下面的方法O(n*m)的求出矩陣的值
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!Gcd[i][j]) {
for (int k = 1; k * i <= n && k * j <= m; k++) {
Gcd[k * i][k * j] = k;
Lcm[k * i][k * j] = i * j * k;
}
}
}
}
此題n*mlog也可以過
求出值後,使用單調佇列O(n*m)的求出最大值相加即可
程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
inline char read() {
#ifdef _WIN32
return getchar();
#endif
static const int IN_LEN = 1 << 18 | 1;
static char buf[IN_LEN], *s, *t;
return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
}
template <typename _Tp> inline READ & operator >> (_Tp&x) {
static char c11, boo;
for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
if(c11 == -1) return *this;
boo |= c11 == '-';
}
for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
boo && (x = -x);
return *this;
}
} in;
const int N = 5050;
int Lcm[N][N];
int q[N];
int mn[N][N];
int main() {
int n, m, k;
in >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!Lcm[i][j]) {
for (int k = 1; k * i <= n && k * j <= m; k++) {
Lcm[k * i][k * j] = i * j * k;
}
}
}
}
for (int i = 1; i <= n; i++) {
int l = 1, r = 0;
for (int j = 1; j <= m; j++) {
while (l <= r && j - q[l] >= k) l++;
while (l <= r && Lcm[i][j] >= Lcm[i][q[r]]) r--;
q[++r] = j;
if (j >= k) mn[i][j] = Lcm[i][q[l]];
}
}
ll ans = 0;
for (int i = k; i <= m; i++) {
int l = 1, r = 0;
for (int j = 1; j <= n; j++) {
while (l <= r && j - q[l] >= k) l++;
while (l <= r && mn[j][i] >= mn[q[r]][i]) r--;
q[++r] = j;
if (j >= k) ans += mn[q[l]][i];
}
}
printf("%lld\n", ans);
return 0;
}
G-Greater and Greater
題面
樣例
輸入
6 3
1 4 2 8 5 7
2 3 3
輸出
2
說明
The two subintervals are [2,8,5],[8,5,7]
題意
給出兩個序列A,B,求A有多少長度為\(len(B)\)的子序列,滿足子序列每一項都大於B
題解
這種資料範圍可以考慮使用bitset卡常,使用bitset後,複雜度會等於\(\frac{n*m}w\)
w一般取32,跟計算機位數有關。
讓每一個b[i]對應一個bitset,這個bitset的第j位為1表示a[j]>b[i],把所有bitset構造出來後,如果b[0]對應的第i位,b[1]對應的第i+1位...b[m-1]對應的第i+m-1位均為1,則說明a陣列從i開始的一段長為m的子區間是滿足答案的,也就對答案貢獻1。
為了方便統計,可以讓b[i]對應的bitset右移i位(i從0開始),這樣如果有一列的與為1,則說明從這一列開始的m長度區間是滿足答案的。
還可以發現,如果把a,b分別排序,同時記錄原位置,開一個pair記錄,first為值,second為位置。那麼這m個bitset是存在遞推關係的,從b最大的開始,較小的數可以直接繼承較大數的bitset,然後將\(a[j].first>b[i].first\)的\(a[j].second\)位置1,就得到了\(b[i].second\)對應的bitset,然後把這m個bitset做&操作,統計最後1的個數即為答案。
程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
inline char read() {
#ifdef _WIN32
return getchar();
#endif
static const int IN_LEN = 1 << 18 | 1;
static char buf[IN_LEN], *s, *t;
return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
}
template <typename _Tp> inline READ & operator >> (_Tp&x) {
static char c11, boo;
for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
if(c11 == -1) return *this;
boo |= c11 == '-';
}
for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
boo && (x = -x);
return *this;
}
} in;
const int N = 2e5 + 50;
bitset<N> ans, bs;
pair<int, int> a[N], b[N];
int main() {
int n, m; in >> n >> m;
for (int i = 0; i < n; i++) {
in >> a[i].first;
a[i].second = i;
}
for (int i = 0; i < m; i++) {
in >> b[i].first;
b[i].second = i;
}
sort(a, a + n);
sort(b, b + m);
ans.set();
for (int i = m - 1, j = n - 1; i >= 0; i--) {
while (j >= 0 && a[j].first >= b[i].first) {
bs.set(a[j].second);
j--;
}
ans &= bs >> b[i].second;
}
printf("%d\n", ans.count());
return 0;
}
H-Happy Triangle
題面
樣例
輸入
8
1 1
3 1
1 1
3 2
3 1
1 2
2 1
3 1
輸出
No
No
Yes
No
題意
q次操作,操作1代表向multiset中插入一個x,操作2代表從multiset中刪除x,操作3詢問multiset中是否存在兩個數與x可以構成三角形,對於每個操作3,輸出yes/no
題解
首先,在回答詢問的時候可以只考慮相鄰的兩個數。因為對於一組a,b,x,不妨設\(a \le b\),如果能構成三角形,a可以換成b的前驅而不影響答案。
對於3操作,分類討論
-
x是最大邊,那麼找到x的兩個前驅a,b,判斷\(a+b>x\)是否成立即可
-
x不是最大邊,那麼需要找到相鄰的兩個數a,b,\(b \ge x, a+x \ge b\)即\(x \ge b - a\),所以對於每個數維護它和它前驅的差,查詢時找到大於x的差的最小值與x比較。
對於第一種情況,維護一個multiset即可,對於第二種情況,可以使用動態開點線段樹,每個點的下標是它的值,權值為它與他前驅的差,使用一個map來判斷增加的值是否重複,刪除是否把一個值刪除完。
程式碼
#include <bits/stdc++.h>
#define mid (l+r>>1)
using namespace std;
typedef long long ll;
struct READ {
inline char read() {
#ifdef _WIN32
return getchar();
#endif
static const int IN_LEN = 1 << 18 | 1;
static char buf[IN_LEN], *s, *t;
return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
}
template <typename _Tp> inline READ & operator >> (_Tp&x) {
static char c11, boo;
for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
if(c11 == -1) return *this;
boo |= c11 == '-';
}
for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
boo && (x = -x);
return *this;
}
} in;
const int N = 2e6 + 50;
const int inf = 0x3f3f3f3f;
multiset<int> st;
map<int, int> mp;
int mn[N << 2], L[N << 2], R[N << 2], tot, rt;
void pushup(int o) {
mn[o] = min(mn[L[o]], mn[R[o]]);
}
void update(int &o, int l, int r, int x, int v) {
if (!o) o = ++tot;
if (l == r) {
mn[o] = v;
return;
}
if (x <= mid) update(L[o], l, mid, x, v);
else update(R[o], mid + 1, r, x, v);
pushup(o);
}
int query(int o, int l, int r, int ql, int qr) {
if (!o) return inf;
if (l == ql && r == qr) return mn[o];
if (qr <= mid) return query(L[o], l, mid, ql, qr);
else if (ql > mid) return query(R[o], mid + 1, r, ql, qr);
else return min(query(L[o], l, mid, ql, mid), query(R[o], mid + 1, r, mid + 1, qr));
}
void ins(int x) {
mp[x]++;
if (!st.size()) {
st.insert(x);
return;
}
if (mp[x] == 1) {
auto it = mp.lower_bound(x);
++it;
if (it != mp.end() && it->second == 1) update(rt, 1, 1e9, it->first, it->first - x);
--it;
int y = -1e9;
if (it != mp.begin()) y = prev(it)->first;
update(rt, 1, 1e9, x, x - y);
}
else if (mp[x] == 2) update(rt, 1, 1e9, x, 0);
st.insert(x);
}
void del(int x) {
auto it = mp.lower_bound(x);
mp[x]--;
int y = -1e9;
if (it != mp.begin()) y = prev(it)->first;
if (mp[x] == 0) {
if ((++it) != mp.end() && it->second == 1) {
update(rt, 1, 1e9, it->first, it->first - y);
}
update(rt, 1, 1e9, x, inf);
mp.erase(x);
}
else if (mp[x] == 1) update(rt, 1, 1e9, x, x - y);
st.erase(st.lower_bound(x));
}
void calc(int x) {
bool ok = false;
if (st.size() < 2) {
puts("No");
return;
}
auto it = st.upper_bound(x);
int a = -inf, b = -inf;
if (it != st.begin()) a = *(--it);
if (it != st.begin()) b = *(--it);
if (a + b > x) ok = true;
if (query(rt, 1, 1e9, x, 1e9) < x) ok = true;
if (ok) puts("Yes");
else puts("No");
}
int main() {
int q; in >> q;
memset(mn, inf, sizeof(mn));
while (q--) {
int op, x; in >> op >> x;
if (op == 1) ins(x);
else if (op == 2) del(x);
else calc(x);
}
return 0;
}
I-Interval
待填坑
J-Just Shuffle
題面
樣例
輸入
3 998244353
2 3 1
輸出
3 1 2
題意
給出一個長度為n的排列\(A\)和k,求一個置換,將$ {1,2,\dots n} \(做k次這個置換後可以得到排列A,求將\) {1,2,\dots n} $做1次這個置換後的值
題解
首先把A所有環求出來,設環長為\(l\),由於k是大質數,所以可以求出k在模\(l\)意義下的逆元\(inv_i\),這樣把這個環逆向轉\(inv_i\)次就可以得到旋轉1次的環,也就是所要求的答案。
逆元可以用尤拉定理求
程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
inline char read() {
#ifdef _WIN32
return getchar();
#endif
static const int IN_LEN = 1 << 18 | 1;
static char buf[IN_LEN], *s, *t;
return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
}
template <typename _Tp> inline READ & operator >> (_Tp&x) {
static char c11, boo;
for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
if(c11 == -1) return *this;
boo |= c11 == '-';
}
for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
boo && (x = -x);
return *this;
}
} in;
const int N = 2e5 + 50;
int a[N];
int f[N], p[N];
int tot;
void euler(int n) {
f[1] = 1;
for (int i = 2; i <= n; i++) {
if (!f[i]) p[++tot] = i, f[i] = i - 1;
for (int j = 1; j <= tot && i * p[j] <= n; j++) {
if (i % p[j] == 0) {
f[i * p[j]] = f[i] * p[j];
break;
}
f[i * p[j]] = f[i] * (p[j] - 1);
}
}
}
int vis[N];
int qpow(int a, int b, int p) {
int ans = 1;
while (b) {
if (b & 1) ans = 1ll * ans * a % p;
a = 1ll * a * a % p;
b >>= 1;
}
return ans;
}
int ans[N];
int main() {
int n, k; in >> n >> k;
euler(n);
for (int i = 1; i <= n; i++) in >> a[i];
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
vis[i] = 1;
int j = i;
vector<int> b;
b.push_back(j);
while (a[j] != i) {
b.push_back(a[j]);
j = a[j];
vis[j] = 1;
}
int r = qpow(k % b.size(), f[b.size()] - 1, b.size());
for (int i = 0; i < b.size(); i++) {
ans[b[i]] = b[(i + r) % b.size()];
}
}
for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
return 0;
}
K-Keyboard Free
待填坑