A:灯光控制
思路:对一条x+k * k1, y+k * k2分类讨论得出能照亮的灯有多少,如果(X, Y)发出的两条射线不在同一个方向,那么就是两条射线能照亮的灯数之和减去1,否则看同一条射线上重复照亮的多少灯,相加之后减去即可。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
ll n, m;
ll solve(ll x, ll y, ll a, ll b) {
if(!a && !b) return 1;
if(!a) {
if(b > 0) return ((m - y) / b) + 1;
else return ((1 - y) / b) + 1;
} else if(!b) {
if(a > 0) return ((n - x) / a) + 1;
else return ((1 - x) / a) + 1;
} else {
if(a > 0 && b > 0) return min((n - x) / a, (m - y) / b) + 1;
else if(a > 0 && b < 0) return min((n - x) / a, (1 - y) / b) + 1;
else if(a < 0 && b < 0) return min((1 - x) / a, (1 - y) / b) + 1;
else return min((1 - x) / a, (m - y) / b) + 1;
}
}
ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
int main() {
ll x, y;
ll a, b, c, d;
while(scanf("%lld %lld %lld %lld", &n, &m, &x, &y) != EOF) {
scanf("%lld %lld %lld %lld", &a, &b, &c, &d);
ll ans1 = solve(x, y, a, b);
ll ans2 = solve(x, y, c, d);
ll ans;
if(a * d == c * b && a * c + b * d > 0) {
if(!a) {
ll g = gcd(b, d);
ll lcm = b * d / g;
if(lcm <= m) ans = ans1 + ans2 - solve(x, y, 0, lcm);
else ans = ans1 + ans2 - 1;
} else if(!b) {
ll g = gcd(a, c);
ll lcm = a * c / g;
if(lcm <= n) ans = ans1 + ans2 - solve(x, y, lcm, 0);
else ans = ans1 + ans2 - 1;
} else {
ll g1 = gcd(a, c), g2 = gcd(b, d);
ll l1 = a * c / g1, l2 = b * d / g2;
if(l1 <= n && l2 <= m) ans = ans1 + ans2 - solve(x, y, l1, l2);
else ans = ans1 + ans2 - 1;
}
} else {
ans = ans1 + ans2 - 1;
}
cout << ans << endl;
}
return 0;
}
B:相似的字符串
思路:对相同长度的字符串哈希26次就好了,为了避免出现哈希值相同的,可以用两个哈希值存储
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
struct P {
ll len, hs1, hs2;
P() {}
P(ll l, ll h1, ll h2) : len(l), hs1(h1), hs2(h2) {}
bool operator < (P p) const {
if(len != p.len) return len < p.len;
if(hs1 != p.hs1) return hs1 < p.hs1;
return p.hs2 < p.hs2;
}
bool operator == (P p) const {
if(len != p.len) return false;
if(hs1 != p.hs1) return false;
if(hs2 != p.hs2) return false;
return true;
}
};
const ll mod1 = 1e15 + 7;
const ll mod2 = 1e14 + 7;
const ll maxn = 1e5 + 10;
map<P, int> mp;
char str[maxn]; int n;
int main() {
while(scanf("%d", &n) != EOF) {
mp.clear();
for(int i = 0; i < n; i++) {
scanf("%s", str);
ll len = strlen(str);
ll s1 = 0, s2 = 0, flag = 0;
for(ll t = 0; t < 26; t++) {
s1 = 0; s2 = 0;
for(int j = 0; j < len; j++) {
ll x = (t + str[j] - 'A') % 26;
s1 = (s1 * 26 + x) % mod1;
s2 = (s2 * 26 + x) % mod2;
}
if(mp.count(P(len, s1, s2))) { flag = 1; break; }
}
if(!flag) mp[P(len, s1, s2)] = 1;
}
int ans = mp.size();
cout << ans << endl;
}
return 0;
}
C:等差子序列
思路:计算每个位置作为右端点能向左扩展到多远,那么线段树中记录下每个节点的等差子序列的区间,按左端点排序,查询区间[ql, qr]的时候,固定了线段树中右端点可以确定哪些在[ql, qr]里面的,左端点要么超出了ql(这些按照右端点最大值取),要么在[ql, qr]里面(这些就是每个点等差子序列区间的长度),所以线段树中维护一个右端点最大值, 一个区间最大值就可以了
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e5 + 10;
const int INF = 1e9 + 10;
using namespace std;
typedef pair<int, int> P;
vector<P> G[maxn * 4], data[maxn * 4];
int n, q, ls[maxn], a[maxn];
void build(int o, int l, int r) {
for(int i = l; i <= r; i++) G[o].push_back(P(i - ls[i] + 1, i));
sort(G[o].begin(), G[o].end());
int sz = G[o].size();
for(int i = 0; i < sz; i++) {
data[o].push_back(P(0, 0));
if(!i) data[o][i].first = G[o][i].second;
else data[o][i].first = max(G[o][i].second, data[o][i - 1].first);
}
data[o][sz - 1].second = G[o][sz - 1].second - G[o][sz - 1].first + 1;
for(int i = sz - 2; i >= 0; i--) data[o][i].second = max(G[o][i].second - G[o][i].first + 1, data[o][i + 1].second);
if(l == r) return ;
int mid = (l + r) >> 1, o1 = o << 1, o2 = o << 1 | 1;
build(o1, l, mid); build(o2, mid + 1, r);
}
int query(int o, int l, int r, int ql, int qr) {
if(l > qr || r < ql) return 0;
if(l >= ql && r <= qr) {
int id = lower_bound(G[o].begin(), G[o].end(), P(ql, 0)) - G[o].begin();
int a1 = 0, a2 = 0;
if(id) a1 = data[o][id - 1].first - ql + 1;
if(id != G[o].size()) a2 = data[o][id].second;
return max(a1, a2);
}
int mid = (l + r) >> 1, o1 = o << 1, o2 = o << 1 | 1;
int p1 = query(o1, l, mid, ql, qr);
int p2 = query(o2, mid + 1, r, ql, qr);
return max(p1, p2);
}
int main() {
while(scanf("%d %d", &n, &q) != EOF) {
a[0] = INF;
for(int i = 0; i < 4 * maxn; i++) { G[i].clear(); data[i].clear(); }
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if(i == 1) ls[i] = 1;
else {
ls[i] = 2;
if(a[i] - a[i - 1] == a[i - 1] - a[i - 2]) ls[i] = ls[i - 1] + 1;
}
}
build(1, 1, n);
while(q--) {
int ql, qr; scanf("%d %d", &ql, &qr);
cout << query(1, 1, n, ql, qr) << endl;
}
}
return 0;
}
D:评论框排版
思路:如果一个集合里保证这些区间不相交, 相连续的已经合并的话,对于插入[L, R]时,首先考虑和他最近的而且在他前面的,有交集的话就合并这两个, 否则不考虑前面的,合并之后逐个考虑后面的区间,把可以合并的逐个合并, 合并的时候用并查集记录下需要往后移动多少就行了
#include<bits/stdc++.h>
typedef long long ll;
const ll maxn = 1e5 + 10;
const ll INF = 1e9 + 10;
using namespace std;
struct pa {
ll l, r, node;
pa() {}
pa(ll l, ll r, ll n) : l(l), r(r), node(n) {}
bool operator < (pa p) const { return l < p.l; }
};
ll l[maxn], r[maxn], pre[maxn];
ll dis[maxn], n, x, y;
char op[20];
set<pa> st;
ll findset(ll x) {
if(x == pre[x]) return x;
ll af = pre[x];
ll fa = findset(pre[x]);
dis[x] = dis[x] + dis[af];
return pre[x] = fa;
}
void add(ll x) {
pa now(l[x], r[x], x);
set<pa>::iterator it;
it = st.upper_bound(now);
pa sta = now;
if(!st.empty() && it != st.begin()) {
it--; pa res = *it;
if(res.r >= l[x] - 1) {
st.erase(it);
ll rr = res.r;
ll ds = rr - l[x] + 1;
dis[x] = ds; pre[x] = res.node;
res.r = r[x] + ds; sta = res;
}
}
while(!st.empty()) {
it = st.lower_bound(sta);
if(it == st.end() || sta.r < (*it).l - 1) break;
pa res = *it; st.erase(it);
pre[res.node] = sta.node;
ll ds = sta.r - res.l + 1;
dis[res.node] = ds;
sta.r = res.r + ds;
}
st.insert(sta);
}
int main() {
while(scanf("%lld", &n) != EOF) {
memset(dis, 0, sizeof dis);
for(int i = 1; i <= n; i++) pre[i] = i;
ll cnt = 0; st.clear();
while(n--) {
scanf("%s", op);
if(op[0] == 'I') {
scanf("%lld %lld", &x, &y);
cnt++;
l[cnt] = x; r[cnt] = x + y - 1;
add(cnt);
} else {
scanf("%lld", &x); findset(x);
ll tot = dis[x];
printf("%lld %lld\n", l[x] + tot, r[x] + tot);
}
}
}
return 0;
}