A Villages: Landlines
题意:有一个电站位于 ,需要在 的范围内设置至少一个电塔将电引出。 个用电处位于 ,需要在其范围 有一电塔才能保证供电。你可以任意设置电塔(与电站、用电所重合也可),但是这些电塔需要用线连接起来。问最少需要多长电线。,,。
解法:题意等效于 个区间:,,然后用电线填补区间中间的空白,问空白长度。对所有区间按左端点排序,求出最长可以连续延申到的右端点,将有交集的区间合并。然后将这些合并后的区间直接填补中间的空白即可。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
long long x, y;
scanf("%d%lld%lld", &n, &x, &y);
vector<pair<long long, long long>> que;
que.emplace_back(x - y, x + y);
for (int i = 1; i < n;i++)
{
scanf("%lld%lld", &x, &y);
que.emplace_back(x - y, x + y);
}
sort(que.begin(), que.end());
long long ans = 0;
long long maxr = que[0].second;
vector<pair<long long, long long>> now;
for (int i = 0; i < n;)
{
int j = i;
long long maxr = que[i].second;
while (j < n && que[j].first <= maxr)
{
maxr = max(maxr, que[j].second);
j++;
}
now.emplace_back(que[i].first, maxr);
i = j;
}
for (int i = 0; i + 1 < now.size();i++)
ans += now[i + 1].first - now[i].second;
printf("%lld", ans);
return 0;
}
B Spirit Circle Observation
题意:给定纯数字序列 ,问存在多少对区间 满足 且 。。
解法:合法的区间一定满足 ,,其中 表示一段连续且完全相同的子串。
考虑一个看似暴力实则正确的做法:建立 SAM,暴力枚举每个节点(即枚举 ),对于每个节点枚举 ,从当前节点开始一个沿着 走下去,另一个沿着 走下去,两边取次数的 ,答案即为当前节点所覆盖长度乘以次数,再乘以上每次枚举 的方案之和。
为什么可以每次都暴力走下去?由于 SAM 的性质保证了其状态数最简,因而每个 字符出边只会作为 被访问一次,然后作为 的延申段被访问一次——它不可能同时被多个状态的相同字符出边所直接指向。对于其他字符出边只会作为 被访问一次。总时间复杂度 。
#include <bits/stdc++.h>
using namespace std;
class SAM
{
const int shift = 48, sigma = 10;
struct node
{
int ch[10];
int len;
int father;
long long cnt;
node()
{
memset(ch, 0, sizeof(ch));
len = father = cnt = 0;
}
} NIL;
vector<node> t;
vector<vector<int>> graph;
int last, ind;
void insert(int c)
{
int p = last;
int np = last = ++ind;
t.push_back(NIL);
t[np].len = t[p].len + 1;
t[np].cnt = 1;
for (; p && !t[p].ch[c]; p = t[p].father)
t[p].ch[c] = np;
if(!p)
t[np].father = 1;
else
{
int q = t[p].ch[c];
if (t[p].len + 1 == t[q].len)
t[np].father = q;
else
{
int nq = ++ind;
t.push_back(t[q]);
t[nq].cnt = 0;
t[nq].len = t[p].len + 1;
t[q].father = t[np].father = nq;
for (; p && t[p].ch[c] == q; p = t[p].father)
t[p].ch[c] = nq;
}
}
}
public:
SAM(string s)
{
last = ind = 1;
t.push_back(NIL);
t.push_back(NIL);
for (auto i : s)
insert(i - shift);
graph.resize(t.size());
for (int i = 2; i <= ind;i++)
graph[t[i].father].push_back(i);
function<void(int)> dfs = [&](int place)
{
for (auto i : graph[place])
{
dfs(i);
t[place].cnt += t[i].cnt;
}
};
dfs(1);
}
long long query()
{
long long ans = 0;
function<void(int, int)> dfs = [&](int place, int father)
{
long long pre = t[place].len - t[t[place].father].len;
for (int i = 0; i < 9;i++)
{
int x = t[place].ch[i], y = t[place].ch[i + 1];
while (x && y)
{
ans += t[x].cnt * t[y].cnt * pre;
x = t[x].ch[9];
y = t[y].ch[0];
}
}
for (auto i : graph[place])
dfs(i, place);
};
t[0].len = -1;
dfs(1, 0);
return ans;
}
};
int main()
{
int n;
string s;
cin >> n >> s;
SAM solve(s);
printf("%lld", solve.query());
return 0;
}
C Grab the Seat!
题意: 至 有一个长度为 的荧幕,在 的范围内已经坐了 个人。 次询问,每次会永久性更新一个人的座位,问更改晚座位后,有多少个座位能够完整的看到荧幕而不会被任何已经坐着的 个人挡住。,。
解法: 非常小因而考虑 或者 的做法。考虑一个人 能够挡住的范围,一定是沿着直线 和直线 所围成的一个类似三角形区域(因为有边界)。
统计答案的时候可以根据每一行能坐多少个座位,因而可以考虑维护出每一行最右( 最大)能坐到哪里。对于荧幕的一个边缘 ,从下到上的枚举每一行已经坐了人的最左侧位置,并不断更新这个人和 的斜率 :若当前这个人和 的斜率不足 ,则表示这个人看 都会被之前某个人影响,因而这个人存在与否是不重要的;如果超过 ,证明从这里开始, 这一射线的右上三角部分都会受这个人的影响,但是坐在下面的( 更小的座位)看 不会受到这个人的影响,因而更新 。本行最右侧能看到 的位置由 给定。对于荧幕的最上侧 同理,只是从上到下的去枚举即可。时间复杂度 。
#include <bits/stdc++.h>
using namespace std;
const int N = 200000, M = 200;
long long x[N + 5], y[N + 5];
int main()
{
int n, m, k, q, id;
long long nowx, nowy;
scanf("%d%d%d%d", &n, &m, &k, &q);
for (int i = 1; i <= k;i++)
scanf("%lld%lld", &x[i], &y[i]);
while(q--)
{
scanf("%d%lld%lld", &id, &nowx, &nowy);
x[id] = nowx;
y[id] = nowy;
vector<long long> minimum(m + 1, n + 1);
for (int i = 1; i <= k;i++)
minimum[y[i]] = min(minimum[y[i]], x[i]);
double k = 0;
vector<int> lim1(m + 1), lim2(m + 1);
for (int i = 1; i <= m; i++)
{
k = max(k, double(i - 1) / minimum[i]);
if (k == 0)
lim1[i] = minimum[i] - 1;
else
lim1[i] = floor((double)(i - 1) / k - 1e-9);
}
k = 0;
for (int i = m; i >= 1;i--)
{
k = min(k, (double)(i - m) / minimum[i]);
if (k == 0)
lim2[i] = minimum[i] - 1;
else
lim2[i] = floor((double)(i - m) / k - 1e-9);
}
long long ans = 0;
for (int i = 1; i <= m;i++)
ans += min({n, lim1[i], lim2[i]});
printf("%lld\n", ans);
}
return 0;
}
D Mocha and Railgun
题意:给定一个半径为 的圆,和一个圆内定点 ,有一中心钉在 ,可以绕着 任意旋转的长度为 的线段 ,问 所对应的弧长最长有多少,距离对应规则如下。保证 离圆弧距离超过 。
解法:可以证明当 时答案最大(利用画图软件观察得到)。剩下的利用三角函数计算即可。
E LTCS
题意:定义最大公共子树为,树上节点的值相同,且树上祖孙、兄弟关系完全相同。给定两个均以 为根的有根树,大小为 ,并给定树上点的值 ,问最大公共子树。。
解法:考虑最朴素的 dp: 表示在第一个树上走到节点 ,第二个树上走到节点 , 表示 节点不一定匹配, 表示一定匹配,的最大公共子树。
对于 ,首先满足 。此外,两个子树需要匹配,考虑对 和 的儿子进行匹配——对 的儿子 和 儿子匹配的边权为 。那么就是进行一次二分图的带权最大匹配,记这一最大匹配为 ,则 ;对于 ,可以从 转移。
可以证明这样做的复杂度为 ,足以通过本题。
G Lexicographical Maximum
题意:给定 ,问 数字按字典序排序,最大值为多少。。
解法:当 时直接输出 ,否则输出 个 。
H Fly
题意:给定长度为 的序列 ,和 条制限: 表示第 个数字 的二进制表示中 位必须为 ,问满足 的长度为 的合法有序数列 的个数。,。
解法:原题即为把 拆分成 个价值为 的物品,每个物品至多只能选一次,求价值小于等于 的方案数。对于此类价值为 形式的,一律使用二进制拆分:即依照 从高到底的顺序考察物品。对于 这一档,总价值可以缩水到 ——因为多余部分不可能利用 填满,因而只能被迫扔掉。同时由于 的保证,总价值必然不会超过 ——。对于 向 转移时,只需要将当前占据的体积扩大一倍,同时对 取 即可。
此外,由于此题 较大,物品数达到了 ,因而朴素的 01 背包无法满足,需要使用生成函数来优化 01 背包计算—— 可以利用递归树做到 的复杂度。
因而总的复杂度为 。
I Chiitoitsu
题意:给定起始 张麻将牌,然后一个人随机摸牌 巡,问期望多少次能自摸七对子。保证起始手牌没有刻子和暗杠。
解法:显然如果手上有对子,摸到第三张也会直接打掉;若没有这一张,也不会留着——换张对推进手牌没有任何意义,因而只有当手上有这张牌才会留着。
考虑 表示已经有 个对子,摸到第 巡,还要期望摸多少巡能自摸。对于当前情况,山里还有 张可能的上张牌——只要不成对的,剩余的三张全在牌山里,因而向听数前进的概率 。因而倒推:,边界情况 。
#include <bits/stdc++.h>
using namespace std;
const long long mod = 1000000007;
long long power(long long a, long long x)
{
long long ans = 1;
while(x)
{
if (x & 1)
ans = ans * a % mod;
a = a * a % mod;
x >>= 1;
}
return ans;
}
long long inv(long long a)
{
return power(a, mod - 2);
}
long long f[8][200];
int main()
{
for (int i = 6; i >= 0;i--)
for (int j = 3 * (13 - 2 * i); j <= 136 - 13;j++)
{
long long p = 3 * (13 - 2 * i) % mod * inv(j) % mod;
f[i][j] = (f[i + 1][j - 1] * p % mod + f[i][j - 1] * (mod + 1 - p) % mod + 1) % mod;
}
int t;
scanf("%d", &t);
for (int o = 1; o <= t;o++)
{
string s;
cin >> s;
int cnt = 0;
map<string, int> vis;
for (int i = 0; i < 13;i++)
vis[s.substr(i * 2, 2)]++;
for (auto i : vis)
if(i.second == 2)
cnt++;
printf("Case #%d: %lld\n", o, f[cnt][123]);
}
return 0;
}
J Serval and Essay
题意:有 个命题,每个命题可以被证明为正确当且仅当其前提条件均被证明为正确。若一个命题没有前置条件,且未被设定为公理,则被认为是错误的。现在可以选定任一个命题认为是公理,问最多可以证明多少个命题是正确的。。
解法:记 表示设定命题 为公理时,所能推断出来的正确集合。则 当且仅当命题 的所有前置条件均在 中。如果把每个前置条件关系看作一条有向边, 集合所有的出边全部合并入 的出边,则 当且仅当 仅有一条入边 。
基于以上的想法,可以考虑启发式合并:首先 ,。然后类似拓扑排序:若一个点 的入边个数仅为 ——,则将 合并进入 ,且将 的所有出边全部归入 中。为了保证复杂度,需要将出边少的点合并进入出边多的点,使用 set
维护每个点的出边信息和 大小。总复杂度 。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t, n;
scanf("%d", &t);
for (int o = 1; o <= t;o++)
{
scanf("%d", &n);
vector<set<int>> reach(n + 1);
vector<set<int>> graph(n + 1);
vector<int> deg(n + 1, 0), siz(n + 1, 1), father(n + 1);
function<int(int)> getfather = [&](int x)
{
return father[x] == x ? x : father[x] = getfather(father[x]);
};
queue<pair<int, int>> q;
auto merge = [&](int u, int v)
{
if (u == v)
return;
if (graph[u].size() < graph[v].size())
swap(u, v);
father[v] = u;
siz[u] += siz[v];
for (auto i : graph[v])
{
if (graph[u].count(i))
{
deg[i]--;
if (deg[i] == 1)
q.emplace(u, i);
}
else
graph[u].insert(i);
}
graph[v].clear();
};
for (int i = 1, x; i <= n;i++)
{
father[i] = i;
scanf("%d", °[i]);
for (int j = 1; j <= deg[i];j++)
{
scanf("%d", &x);
graph[x].insert(i);
}
if (deg[i] == 1)
q.emplace(i, x);
}
while(!q.empty())
{
auto tp = q.front();
q.pop();
merge(getfather(tp.first), getfather(tp.second));
}
int ans = 0;
for (int i = 1; i <= n;i++)
ans = max(ans, siz[getfather(i)]);
printf("Case #%d: %d\n", o, ans);
}
return 0;
}