[JSOI2010]冷冻波 题解
题目地址:洛谷:【P4048】[JSOI2010]冷冻波 – 洛谷、BZOJ: …
May all the beauty be blessed.
题目地址:洛谷:【P3425】[POI2005]KOS-Dicing – 洛谷、BZOJ:Problem 1532. — [POI2005]Kos-Dicing
掷骰子是一种双人游戏,它的结果是完全随机的。最近它在整个Byteotia变得非常流行。在Byteotia的首都甚至有一个特别的掷骰子业余爱好者俱乐部。俱乐部的老主顾们花时间互相聊天并每隔一阵子就和一个随机选择的对手玩这他们最喜欢的游戏。一天中赢得最多游戏的人会得到“幸运者”头衔。有时晚上俱乐部很安静,只有很少的比赛。这是哪怕赢一场也能获得“幸运者”头衔的时间。
很久很久以前有一个很不走运的人,叫Byteasar,赢得了这个光荣的头衔。他被深深地震惊了以至于完全忘了他已经赢了多少场。现在他想知道他有多幸运,以及幸运之神是否最终会向他微笑——也许他的运气会变好?他确切地知道在那个幸运的晚上有多少场游戏以及是谁玩的。然而,他不知道结果。Byteasar希望查明至少要赢几场才能获得“幸运者”头衔。做个好人,帮他满足他的好奇心吧!
任务:写一个程序:
对于每场游戏从stdin读入这场游戏的一对玩家 找到最小的数k,使得存在一个游戏结果的集合,其中赢最多的玩家赢了k场。向stdout输出数k和找到的集合中游戏的结果
Dicing 是一个两人玩的游戏,现在有一个非常不走运的家伙,他想成为那个玩的最好的人,他现在知道了所有比赛的安排,他想知道,在最好的情况下,他最少只需要赢几场就可以赢得冠军,即他想知道比赛以后赢的最多的那个家伙最少会赢多少场.
输入格式:
stdin的第一行有一个一对由一个空格分开整数n和m (1 <= n, m <= 10000) 。n表示玩家数,m表示游戏数。玩家从1到n编号。在接下来的m行中有每场游戏的一对玩家的编号,由一个空格分开,描述了游戏的序列。一对玩家可能会在这个序列中多次出现。
输出格式:
stdout的第一行应该包含一个确定的数k。对于在输入的第i行指定的一对玩家a, b,如果在找到的结果集合中a胜过b,则在输出的第i行输出1, 否则输出0.
输入样例#1:
4 4 1 2 1 3 1 4 1 2
输出样例#1:
1 0 0 0 1
考虑二分答案然后判定。判定可以采用网络流,建立源→每场游戏→游戏玩家→汇的网络,源→游戏及游戏→玩家的边容量为1,玩家→汇的边容量为二分的答案。如果答案是x,说明游戏中每个人胜出的次数都不大于x,因此只需在该网络上判定是否满流,满流说明该答案可行。
复杂度玄学。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <queue>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 20005, INF = 1e9;
struct Edge {
int to, cap, nxt;
} gra[MAXN << 4];
int head[MAXN], tot;
inline void addedge(int u, int v, int cap) {
gra[tot] = Edge {v, cap, head[u]}; head[u] = tot++;
gra[tot] = Edge {u, 0, head[v]}; head[v] = tot++;
}
int level[MAXN];
inline bool bfs(int s, int t) {
memset(level, -1, sizeof(level));
std::queue<int> que;
que.push(s); level[s] = 0;
while(!que.empty()) {
int u = que.front(); que.pop();
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(gra[i].cap && level[v] == -1) {
level[v] = level[u] + 1;
if(v == t) return true;
que.push(v);
}
}
}
return level[t] != -1;
}
int cur[MAXN];
inline int dfs(int u, int t, int left) {
if(u == t || !left) return left;
int flow = 0;
for(int &i = cur[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(level[v] == level[u] + 1 && gra[i].cap) {
int f = dfs(v, t, std::min(left, gra[i].cap));
if(f) {
flow += f; left -= f;
gra[i].cap -= f; gra[i ^ 1].cap += f;
if(!left) break;
}
}
}
return flow;
}
inline int dinic(int s, int t) {
int flow = 0;
while(bfs(s, t)) {
memcpy(cur, head, sizeof(head));
int f;
while(f = dfs(s, t, INF)) flow += f;
}
return flow;
}
int n, m, a[MAXN], b[MAXN], S, T;
inline bool check(int x) {
tot = 0; memset(head, -1, sizeof(head));
for(int i = 1; i <= n; i++) {
addedge(i, T, x);
}
for(int i = 1; i <= m; i++) {
addedge(S, i + n, 1);
addedge(i + n, a[i], 1);
addedge(i + n, b[i], 1);
}
return dinic(S, T) >= m;
}
int main() {
n = readint(); m = readint(); S = n + m + 1; T = S + 1;
for(int i = 1; i <= m; i++) {
a[i] = readint(); b[i] = readint();
}
int l = 0, r = m, mid;
while(r - l > 1) {
mid = (l + r) >> 1;
if(check(mid)) r = mid; else l = mid;
}
printf("%d\n", r);
check(r);
for(int i = 1; i <= m; i++) {
for(int j = head[i + n]; ~j; j = gra[j].nxt) {
int v = gra[j].to;
if(!gra[j].cap) {
printf("%d\n", v == a[i]); break;
}
}
}
return 0;
}
题目地址:洛谷:【P3674】小清新人渣的本愿 – 洛谷
给你一个序列a,长度为n,有m次操作,每次询问一个区间是否可以选出两个数它们的差为x,或者询问一个区间是否可以选出两个数它们的和为x,或者询问一个区间是否可以选出两个数它们的乘积为x ,这三个操作分别为操作1,2,3
选出的这两个数可以是同一个位置的数
输入格式:
第一行两个数n,m
后面一行n个数表示ai
后面m行每行四个数opt l r x
opt表示这个是第几种操作,l,r表示操作的区间,x表示这次操作的x
输出格式:
对于每个询问,如果可以,输出hana,否则输出bi
输入样例#1:
10 10 1 1 8 9 9 1 1 1 1 9 3 5 9 42 2 1 3 14 2 3 5 2 2 3 3 6 1 6 10 18 3 4 9 14 2 1 4 22 3 1 3 32 2 5 6 32 3 1 9 17
输出样例#1:
bi bi bi bi bi bi bi bi bi bi
输入样例#2:
5 5 1 1 2 3 4 2 1 1 2 1 1 2 2 3 1 1 1 3 5 5 16 1 2 3 4
输出样例#2:
hana bi hana hana bi
定义c为每次的x和ai中的最大值,ai >= 0,每次的x>=2
对于10%的数据,n,m,c <= 100
对于另外10%的数据,n,m,c <= 3000
对于另外10%的数据,只有1操作
对于另外10%的数据,只有2操作
对于另外10%的数据,只有3操作
对于100%的数据,n,m,c <= 100000
参考资料:题解 P3674 【小清新人渣的本愿】 – zcysky – 洛谷博客
莫队和bitset的完美结合。
我们用bitset来维护区间内是否有这个数的状态,但是要开两个bitset,一个正着存,一个反着存。减法就枚举减数找被减数,具体来讲就是bs & (bs >> x)这样判断一下,加法就用bs & (rbs & (MAXN – x))其中rbs是倒着存的bitset。乘法比较麻烦,考虑枚举每个因数判断是否存在。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <bitset>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 100005;
int n, m, block, a[MAXN], cnt[MAXN];
bool ans[MAXN];
std::bitset<MAXN> bs1, bs2;
inline int bid(int pos) {
return pos / block + 1;
}
inline void add(int x) {
if(cnt[x]++ == 0) bs1[x] = bs2[MAXN - x] = 1;
}
inline void del(int x) {
if(--cnt[x] == 0) bs1[x] = bs2[MAXN - x] = 0;
}
struct Query {
int op, l, r, x, id;
} qur[MAXN];
inline bool cmp(Query a, Query b) {
return bid(a.l) == bid(b.l) ? a.r < b.r : a.l < b.l;
}
int main() {
n = readint(); m = readint(); block = sqrt(n);
for(int i = 1; i <= n; i++) {
a[i] = readint();
}
for(int i = 1; i <= m; i++) {
qur[i].op = readint(); qur[i].l = readint(); qur[i].r = readint();
qur[i].x = readint(); qur[i].id = i;
}
std::sort(qur + 1, qur + m + 1, cmp);
int l = 1, r = 0;
for(int i = 1; i <= m; i++) {
for(; l < qur[i].l; l++) del(a[l]);
for(; l > qur[i].l; l--) add(a[l - 1]);
for(; r < qur[i].r; r++) add(a[r + 1]);
for(; r > qur[i].r; r--) del(a[r]);
if(qur[i].op == 1) {
if((bs1 & (bs1 << qur[i].x)).any()) ans[qur[i].id] = true;
} else if(qur[i].op == 2) {
if((bs1 & (bs2 >> (MAXN - qur[i].x))).any()) ans[qur[i].id] = true;
} else {
for(int j = 1; j * j <= qur[i].x; j++) {
if(qur[i].x % j == 0 && bs1[j] && bs1[qur[i].x / j]) {
ans[qur[i].id] = true; break;
}
}
}
}
for(int i = 1; i <= m; i++) {
puts(ans[i] ? "hana" : "bi");
}
return 0;
}
题目地址:洛谷:【P3172】[CQOI2015]选数 – 洛谷、BZOJ:Problem 3930. — [CQOI2015]选数
我们知道,从区间[L,H](L和H为整数)中选取N个整数,总共有(H-L+1)^N种方案。小z很好奇这样选出的数的最大公约数的规律,他决定对每种方案选出的N个整数都求一次最大公约数,以便进一步研究。然而他很快发现工作量太大了,于是向你寻求帮助。你的任务很简单,小z会告诉你一个整数K,你需要回答他最大公约数刚好为K的选取方案有多少个。由于方案数较大,你只需要输出其除以1000000007的余数即可。
求区间[L, H]中选择N个整数(可重),求选出数组成的可重集gcd为K的方案数%1e9+7的值。
输入格式:
输入一行,包含4个空格分开的正整数,依次为N,K,L和H。
输出格式:
输出一个整数,为所求方案数。
输入样例#1:
2 2 2 4
输出样例#1:
3
样例解释
所有可能的选择方案:(2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)
其中最大公约数等于2的只有3组:(2, 2), (2, 4), (4, 2)
对于100%的数据,1<=N,K<=10^9,1<=L<=H<=10^9,H-L<=10^5
参考资料:题解 P3172 【[CQOI2015]选数】 – xyz32768 的博客 – 洛谷博客
我们观察到L与H虽然很大,但是H-L并不大,这是一个我们能利用的地方。首先,可以先行缩小区间范围,即令 L' = \lceil \frac{L}{K} \rceil, H' = \lfloor \frac{H}{K} \rfloor ,问题转变为[L’, H’]区间内选出N个gcd为1的数字的方案数了。
我们考虑一个DP+去重的思想,令f[i]表示含公约数为i且选出的数不全相同的方案数,令[L’, H’]中i的倍数的个数为x,则f[i] = x^n - x。而我们想求的是g[i]表示最大公约数为i且选出的数不全相同的方案数中的g[1],我们容易知道g[i] = f[i] - g[2i] - g[3i] - \cdots,因此可以通过反推求出g[i],从而推得g[1]。f定义中,要求不全相同的原因是如果含相同数字,是因为两两互质肯定不存在选出的数都相同的情况。需要注意的是,如果此L’=1,还可以全选1,因此答案要+1。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 100005, MO = 1e9 + 7;
inline LL fpow(LL n, LL k) {
LL res = 1;
while(k) {
if(k & 1) res = res * n % MO;
n = n * n % MO;
k >>= 1;
}
return res;
}
LL n, k, l, h, dp[MAXN];
int main() {
n = readint(); k = readint(); l = readint(); h = readint();
if(l % k) l = l / k + 1; else l /= k; h /= k;
if(l > h) {
puts("0"); return 0;
}
for(int i = 1; i <= h - l; i++) {
LL ll = l, hh = h;
if(ll % i) ll = ll / i + 1; else ll /= i; hh /= i;
if(ll > hh) continue;
dp[i] = ((fpow(hh - ll + 1, n) - (hh - ll + 1)) % MO + MO) % MO;
}
for(int i = h - l; i; i--) {
for(int j = i << 1; j <= h - l; j += i) {
dp[i] = ((dp[i] - dp[j]) % MO + MO) % MO;
}
}
if(l == 1) dp[1] = (dp[1] + 1) % MO;
printf("%lld", dp[1]);
return 0;
}