CSP-S模拟2(联考)
差点又双叒叕模拟退役
上来先\(\% T2\), 然后感觉就差一点, 最后搞出来就十点多了..
然后心态一度爆炸,有点小摆烂,上厕所冷静一下觉得还有时间,能抢救一下
然后开了 \(T1\), 没啥思路瞎搞,开根号在附近找数用 \(pow\),发现大数据丢精异常严重 $pow(n, 1.0 / 1) $比 \(n\)大了好几百你敢信, 看一下时限\(6s\),于是手写二分, 然后打挂了一堆细节,调完就 \(11:22\) 了
看\(T3\), 感谢\(T3\),上来想到主席树二分直接莽, 然后一遍码对了? \(11:44\)拿到 \(95pts\)(主席树空间不对),防止了模拟退役
\(T4\)打了个\(DP\),显然没打过,不过离正解就差了亿点
感觉上来搞 \(T2\) 策略有点问题,还好切掉了,并且稳住了心态
A. 谜之阶乘
枚举相邻的数的个数\(i\),然后在\(^i\sqrt n\)左右找数, \(pow\)精度有问题,手写二分,但是数很大会炸 \(long long\), 所以先判 \(n / now >= mid\)再 \(now = now * mid\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = -1;
ll fac[55];
void init(){
fac[0] = 1;
for(int i = 1; i <= 20; ++i)fac[i] = fac[i - 1] * i;
}
inline ll read(){
ll x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
ll a[105], b[105];
ll mr(ll n, int i){
ll l = 1, r = n, ans = 1;
while(l <= r){
ll mid = (l + r) >> 1;
ll now = 1; bool f = 1;
for(int j = 1; j <= i; ++j){
if(n / now >= mid)now = mid * now;
else now = n + 1;
if(now >= n || now < 0){
r = mid - 1; ans = mid;
f = 0; break;
}
}
if(f)l = mid + 1;
}
return ans;
}
void solve(){
ll n = read();
if(n == 1){printf("-1\n");return;}
int cnt = 0, mx = 0;
for(int i = 1; i <= 20; ++i)if(fac[i + 1] > n){mx = i; break;}
if(fac[mx] == n){
++cnt;
a[cnt] = mx;
b[cnt] = 1;
}
for(int i = mx - 1; i > 0; --i){
ll r = mr(n, i);
ll l = max(3ll, r - i - i - i);
for(ll j = l; j <= r; ++j){
ll now = 1;
for(ll p = 0; p < i; ++p){
now = now * (j + p);
if(now > n)break;
if(now < 0)break;
}
if(now == n){
++cnt;
a[cnt] = j + i - 1;
b[cnt] = j - 1;
break;
}
}
}
printf("%d\n",cnt);
for(int i = 1; i <= cnt; ++i)printf("%lld %lld\n",a[i], b[i]);
}
int main(){
init();
int t = read();
for(int ask = 1; ask <= t; ++ask)solve();
return 0;
}
B. 子集
构造很有意思,但是很费时间,很是人类智慧,所以下次。。。
还是先试试逝世
设 \(sum = n * (n - 1) / 2\)
\(k \nmid sum\) 或者 \(sum / k < n\)无解
\(n/ k\) 为偶数的话对应首尾项相加,然后得到 \(n/ 2\)个和相同的二元组,随便分一下就行
对于 \(n / k\) 为奇数的,每\(k\)项写成一行,每行的 \(k\) 个分入不同的集合里
定义这\(k\)列的权值为 \(- k / 2, -k/2 + 1 ...... 0 ...... k/ 2 - 1, k / 2\)
特殊处理前三行,后面的\(Z\)字形,(就是 \(x, -x, x , -x\)这样取权值)
对于前三行,先考虑前两行,我们先强制 \(x, -x\)配对, 这样第三行需要的都是 \(0\)
考虑对 \((x, -x) (y, -y)\)交换为 \((x, -y)(y, -x)\)那么他们需要的第三行就是 \(|x - y|, -|x - y|\)
那么我们只需要交换距离为 \(1, 2 .... (k + 1)/ 2\) 的不同的二元组的第二项,发现可以对应\(reverse[1, (k + 1)/ 2][(k + 1)/ 2 +1, k]\)两个区间对应的第二行元素即可
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
ll n, k, sum;
int ls[1000005];
void nosolution(){printf("No\n");}
void solve(){
if(k == 1){
printf("Yes\n");
for(int i = 1; i <= n; ++i)printf("%d ",i);printf("\n");return;
}
sum = 1ll * n * (n + 1) / 2;
if(sum % k)return nosolution();
if(sum / k < n) return nosolution();
if((n / k) & 1){
printf("Yes\n");
int s = (1 + k * 3) * (k * 3) / 2 / k;
for(int i = 1; i <= k; ++i)ls[i] = k + k + 1 - i;
reverse(ls + 1, ls + (k + 1) / 2 + 1);
reverse(ls + (k + 1) / 2 + 1, ls + k + 1);
for(int i = 1; i <= k; ++i){
printf("%d %d %d ",i, ls[i], s - i - ls[i]);
int dt = (i + 1) / 2, f = i % 2;
for(int j = 4; j <= n / k; ++j){
if(f)printf("%lld ", (j - 1) * k + dt);
else printf("%lld ",j * k - dt + 1);
f = 1 - f;
}
printf("\n");
}
return;
}else{
printf("Yes\n");
int cnt = 1;
for(int i = 1; i <= k; ++i){
for(int j = n / k / 2; j; --j){
printf("%d %lld ",cnt, n - cnt + 1);
++cnt;
}
printf("\n");
}
return;
}
}
int main(){
int t = read();
for(int ask = 1; ask <= t; ++ask){
n = read(), k = read();
solve();
}
return 0;
}
C. 混凝土粉末
主席树 + 标记永久化 + 二分 + \(22min\)= \(95pts\)
得分效率奇高
95pts
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = 1000005;
inline ll read(){
ll x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
int n, q;
int root[maxn], tot;
struct tree{
struct node{
int l, r; ll val;
}t[maxn * 40];
int cnt;
void insert(int &x, int hrt, int l, int r, int L, int R, ll hi){
if(!x) x = ++cnt, t[x] = t[hrt];
if(L <= l && r <= R){
t[x].val += hi;
return;
}
int mid = (l + r) >> 1;
if(L <= mid){
t[x].l = 0;
insert(t[x].l = 0, t[hrt].l, l, mid, L, R, hi);
}
if(R > mid){
t[x].r = 0;
insert(t[x].r, t[hrt].r, mid + 1, r, L, R, hi);
}
}
ll query(int x, int l, int r, int pos){
if(!x)return 0;
if(l == r)return t[x].val;
int mid = (l + r) >> 1;
if(pos <= mid)return t[x].val + query(t[x].l, l, mid, pos);
else return t[x].val + query(t[x].r, mid + 1, r, pos);
}
}t;
int bound(int r){
int x = read(), l = 1; ll y = read();
if(t.query(root[r], 1, n, x) < y)return 0;
int ans = r;
while(l <= r){
int mid = (l + r) >> 1;
if(t.query(root[mid], 1, n, x) >= y)ans = mid, r = mid - 1;
else l = mid + 1;
}
return ans;
}
int main(){
n = read(), q = read();
for(int i = 1; i <= q; ++i){
int op = read();
if(op & 1){
int l = read(), r = read();
ll h = read();
t.insert(root[i], root[i - 1], 1, n, l, r, h);
}else{
root[i] = root[i - 1];
printf("%d\n",bound(i));
}
}
return 0;
}
正解扫描线,对每个位置分开二分,用树状数组维护每个时间对应的高度
对应修改\(time, l, r,h\)在 \(l\)位置对\(time\)时刻 \(+h\), \(r + 1\) 位置对 \(time\)时刻 \(-h\)这样
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
const int maxn = 1000005;
inline ll read(){
ll x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
int n, q;
struct tree{
ll t[maxn];
int lowbit(int x){return x & -x;}
void add(int x, ll h){
while(x <= q){
t[x] += h;
x += lowbit(x);
}
}
ll query(int x){
ll ans = 0;
while(x){
ans += t[x];
x -= lowbit(x);
}
return ans;
}
}t;
struct node{int time; ll val;};
vector<node>modify[maxn], query[maxn];
int ans[maxn], tot;
bool flag[maxn];
int bound(node x){
if(t.query(x.time) < x.val)return 0;
int l = 1, r = x.time;
int ans = r;
while(l <= r){
int mid = (l + r) >> 1;
if(t.query(mid) >= x.val)ans = mid, r = mid - 1;
else l = mid + 1;
}
return ans;
}
int main(){
n = read(), q = read();
for(int i = 1; i <= q; ++i){
int op = read();
if(op & 1){
int l = read(), r = read();
ll h = read();
modify[l].push_back({i, h});
modify[r + 1].push_back({i, -h});
}else{
int x = read();
query[x].push_back({i, read()});
flag[i] = 1;
}
}
for(int i = 1; i <= n; ++i){
for(node x : modify[i])t.add(x.time, x.val);
for(node x : query[i])ans[x.time] = bound(x);
}
for(int i = 1; i <= q; ++i)if(flag[i])printf("%d\n",ans[i]);
return 0;
}
D. 排水系统
注:\(cd_{x}\) 表示出度
\(g_{x, 0}\)表示推到 \(x\)的原流(没有损坏), \(g_{x, 1}\)表示期望流
在拓扑过程中,可以以均等概率直接推送
然后考虑如果断掉了当前点的某条出边对其他点的贡献
断边概率 \(a/sum\) 断边对其他边流量的影响\(g_{x, 0} / cd[x]/(cd[x] - 1)\)
把该点出边的这部分 \(\sum\) 起来,然后再扫一遍,减去自己的就是其他断边对该点新推流的贡献,还有 \(a/sum\) 的概率断该边需要减去 \(g_{x,0}/cd[x]\)
感谢 \(Delov\) 大佬的巨大帮助
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = 200005;
const int maxm = 500005;
const int mod = 998244353;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
struct edge{
int to, net, val;
}e[maxm];
int head[maxn], tot;
void add(int u, int v, int w){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
e[tot].val = w;
}
int n, m, r, k, inv;
int g[maxn][2], rd[maxn], cd[maxn], iv[maxm];
queue<int>q;
signed main(){
n = read(), m = read(), r = read(), k = read();
iv[1] = 1;
for(int i = 2; i <= k; ++i)iv[i] = 1ll * (mod - mod / i) * iv[mod % i] % mod;
int suma = 0;
for(int i = 1; i <= k; ++i){
int u = read(), v = read(), w = read();
add(u, v, w); suma = (suma + w) % mod;
++rd[v]; ++cd[u];
}
inv = qpow(suma, mod - 2);
for(int i = 1; i <= n; ++i)if(!rd[i])q.push(i);
for(int i = 1; i <= m; ++i)g[i][0] = 1, g[i][1] = 1;
while(!q.empty()){
int x = q.front(); q.pop();
int sum = 0;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
--rd[v]; if(rd[v] == 0)q.push(v);
g[v][1] = (g[v][1] + 1ll * g[x][1] * iv[cd[x]] % mod) % mod;
g[v][0] = (g[v][0] + 1ll * g[x][0] * iv[cd[x]] % mod) % mod;
sum = (sum + 1ll * g[x][0] * inv % mod * e[i].val % mod * iv[cd[x] - 1] % mod * iv[cd[x]]) % mod;
}
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
g[v][1] = (g[v][1] + sum - 1ll * g[x][0] * inv % mod * e[i].val % mod * iv[cd[x] - 1] % mod * iv[cd[x]] % mod + mod) % mod;
g[v][1] = (g[v][1] + mod - 1ll * g[x][0] * inv % mod * e[i].val % mod * iv[cd[x]] % mod) % mod;
}
}
for(int i = n - r + 1; i <= n; ++i)printf("%d ",g[i][1]);
return 0;
}