期末考试
sol
因为时间范围很小,所以可以利用单调性求出对于每一个时间$t$,当最晚的成绩公布时间为$t$时学生产生的不满意度总和$f_t$和让所有课程的公布时间不大于$t$的前提下课程产生的最小不满意度$g_t$.复杂度$O(nlogn)$,瓶颈是排序.
但是上面那个做法太不优雅了.我们可以发现$g_t$和$f_t$差分之后的数组都是单调不减,也就是$f_t+g_t$差分之后单调不减,也就意味着$f_t+g_t$这个数列是单谷数列.我们在时间范围上三分数列极小值即可.
然后因为三分太慢获得了LOJ倒数
#include<bits/stdc++.h>
using namespace std;
int read(){
int a = 0; char c = getchar(); bool f = 0;
while(!isdigit(c)){f = c == '-'; c = getchar();}
while(isdigit(c)){
a = a * 10 + c - 48; c = getchar();
}
return f ? -a : a;
}
#define int long long
const int _ = 1e5 + 7; int A , B , C , N , M , T[_] , P[_];
int max(int a , int b){return a > b ? a : b;}
int calc(int x){
int sum = 0 , cntl = 0 , cntr = 0;
for(int i = 1 ; i <= N ; ++i) sum += max(x - T[i] , 0) * C;
for(int i = 1 ; i <= M ; ++i) P[i] <= x ? cntl += x - P[i] : cntr += P[i] - x;
return A > B ? sum + cntr * B : sum + min(cntl , cntr) * A + max(cntr - cntl , 0) * B;
}
signed main(){
cin >> A >> B >> C >> N >> M;
for(int i = 1 ; i <= N ; ++i) cin >> T[i];
for(int i = 1 ; i <= M ; ++i) cin >> P[i];
int L = 1 , R = 1e5;
if(C == 1e16) for(int i = 1 ; i <= M ; ++i) R = min(R , T[i]);
while(L < R){int mid = (L + R) >> 1; calc(mid) < calc(mid + 1) ? R = mid : L = mid + 1;}
cout << calc(L) << endl; return 0;
}
相逢是问候
sol
根据拓展欧拉定理,$c^{c^{c^{...^{a_i}}}} \mod p = c^{c^{c^{...^{a_i}}}
mod\ \varphi(p)}$(当然这里省略了最后可能需要加上的$\varphi(p)$),那么我们不断令$p = \varphi(p)$,直到$p=1$,设做这样的操作的次数为$count$,则当某一个位置被操作$count+1$次之后值就被固定了(值得注意的是这里一定是$count+1$,因为$a_i$可能为$0$,但是$c^{a_i}$不可能为$0$).
那么我们维护线段树,当某一个区间内所有数的操作次数都$\geq count + 1$就不做操作,否则向下递归.当到达叶子节点时暴力更新,这样的复杂度是$O(n\ log^2p\ logw)$的,可以通过$90$分.
最后可以发现快速幂中的底数和我们需要取模的数都是给定的,所以可以用一个常见的$O(\sqrt p)-O(1)$的快速幂方法:对于一个给定的$p$,设$f_j$表示$c^j \mod p$,$g_j$表示$c^{Sj} \mod p$,其中$S = \sqrt{p}$,那么$c^k \mod p = f_{k \mod S} \times g_{k / S} \mod p$.
在预处理$f$和$g$数组的同时记录一下在不取模的情况下快速幂的实际值是否$\geq p$就可以判断要不要在最后加上一个$p$.
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int _ = 5e4 + 3; int N , P , M , C , T , arr[_]; vector < int > mu;
int pow1[61][_] , pow2[61][_]; bool flg1[61][_] , flg2[61][_];
int poww(int a , int b , int p){
int times = 1; bool flg = 0;
while(b){
if(b & 1){flg |= times * a >= p; times = times * a % p;}
flg |= a * a >= p; a = a * a % p; b >>= 1;
}
return times + (p != P && flg) * p;
}
namespace segt{
int cnt[_] , sum[_ << 2]; bool allv[_ << 2];
#define mid ((l + r) >> 1)
#define lch (x << 1)
#define rch (x << 1 | 1)
void init(int x , int l , int r){
if(l == r){cin >> arr[l]; sum[x] = arr[l]; return;}
else{init(lch , l , mid); init(rch , mid + 1 , r); sum[x] = sum[lch] + sum[rch];}
}
void modify(int x , int l , int r , int L , int R){
if(allv[x] || l > R || r < L) return;
if(l == r){
++cnt[l]; int val = poww(C , arr[l] , mu[cnt[l] - 1]);
for(int i = cnt[l] - 2 ; i >= 0 ; --i){
int p = val % T , q = val / T;
val = pow1[i][p] * pow2[i][q] % mu[i] + (bool)i * (flg1[i][p] || flg2[i][q] || pow1[i][p] * pow2[i][q] >= mu[i]) * mu[i];
}
sum[x] = val; allv[x] = mu[cnt[l] - 1] == 1; return;
}
modify(lch , l , mid , L , R); modify(rch , mid + 1 , r , L , R);
allv[x] = allv[lch] & allv[rch]; sum[x] = sum[lch] + sum[rch];
}
int qry(int x , int l , int r , int L , int R){
if(l >= L && r <= R) return sum[x];
int sum = 0;
if(mid >= L) sum = qry(lch , l , mid , L , R);
if(mid < R) sum += qry(rch , mid + 1 , r , L , R);
return sum;
}
}
int poww(int a , int b , int p , bool &flg){
int times = 1;
while(b){
if(b & 1){flg |= times * a >= p; times = times * a % p;}
flg |= a * a >= p; a = a * a % p; b >>= 1;
}
return times;
}
void initmu(){
mu.push_back(P); T = sqrt(2 * P) + 1;
while(*--mu.end() != 1){
int t = *--mu.end() , tmp = t , id = mu.size() - 1;
pow1[id][0] = pow2[id][0] = 1;
pow1[id][1] = poww(C , 1 , t , flg1[id][1]); pow2[id][1] = poww(C , T , t , flg2[id][1]);
for(int i = 2 ; i <= T ; ++i){
pow1[id][i] = pow1[id][i - 1] * pow1[id][1] % t;
pow2[id][i] = pow2[id][i - 1] * pow2[id][1] % t;
flg1[id][i] = flg1[id][i - 1] | (pow1[id][i - 1] * pow1[id][1] >= t);
flg2[id][i] = flg2[id][i - 1] | (pow2[id][i - 1] * pow2[id][1] >= t);
}
for(int i = 2 ; i * i <= tmp ; ++i)
if(tmp % i == 0){
t = t / i * (i - 1); while(tmp % i == 0) tmp /= i;
}
if(tmp - 1) t = t / tmp * (tmp - 1);
mu.push_back(t);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
ios::sync_with_stdio(0); cin >> N >> M >> P >> C; segt::init(1 , 1 , N); initmu();
for(int i = 1 ; i <= M ; ++i){
int tp , l , r; cin >> tp >> l >> r;
if(tp) printf("%lld\n" , segt::qry(1 , 1 , N , l , r) % P);
else segt::modify(1 , 1 , N , l , r);
}
return 0;
}
组合数问题
sol
设$f_{i,j}$表示$\sum\limits_{p \in [0,i] , p \mod k = j}C_{i}^p$,转移考虑$C_i^j = C_{i-1}^j+C_{i-1}^{j-1}$,就有$f_{i,j} = f_{i-1,(j+k-1) \mod k} + f_{i-1,j}$,答案是$f_{n,r}$.
不难发现这个东西可以矩阵转移,套一个矩阵快速幂即可.复杂度$O(k^3logn)$.
#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;
#define int long long
int N , P , R , K;
struct matrix{
int a[50][50];
matrix(){memset(a , 0 , sizeof(a));}
int* operator [](int x){return a[x];}
matrix operator *(matrix b){
matrix c;
for(int i = 0 ; i < K ; ++i)
for(int j = 0 ; j < K ; ++j)
for(int k = 0 ; k < K ; ++k)
c[i][k] = (c[i][k] + 1ll * a[i][j] * b[j][k]) % P;
return c;
}
}S , T;
signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
cin >> N >> P >> K >> R; N *= K;
S[0][0] = 1;
for(int i = 0 ; i < K ; ++i)
++T[i][i] , ++T[i][(i + 1) % K];
while(N){
if(N & 1) S = S * T;
T = T * T; N >>= 1;
}
cout << S[0][R];
return 0;
}
摧毁树状图
sol
题目相当于选出两条至多在一个节点相交的路径使得将这些点删掉之后连通块数量最大.以下不作说明的情况下均认为$1$为根.
首先对于每一个点树形DP得到$mxlen_i , mxnum_i$,$mxlen_i$表示从点$i$向下延伸出一条路径能够在点$i$的子树中获得的最大连通块数量,$mxnum_i$表示对于点$i$子树中的所有路径,设路径深度最浅的点为$x$,则在$x$子树中删除这条路径能够获得的最大连通块数量的最大值.
然后再对于每一个点通过换根DP求出$uplen_i , upnum_i$,$uplen_i$表示以$fa_i$为原树的根,去掉点$i$所在子树,从$fa_i$向下延伸出一条路径能够获得的最大连通块数量,$upnum_i$则表示在同样的情况下对于$fa_i$子树内的所有路径,设路径深度最浅的点为$x$,则在$x$子树中删除这条路径能够获得的最大连通块数量的最大值.
接下来分三种情况讨论:
A.两条路径距离最近的点的距离为$1$:这两个点一定是一条边的两端.我们枚举边,相当于枚举$x$,考虑边$(x,fa_x)$.对于这种情况一定是一条路径在$x$的子树中,一条路径在以$fa_x$为根去掉$x$所在子树构成的树中,两者的最大连通块数量正好对应$mxnum_x$和$upnum_x$.所以用$mxnum_x+upnum_x$更新答案即可;
B.两条路径距离最近的点的距离为$0$:这个点只会有一个,考虑枚举这个点$x$.以$x$为树根,那么相当于需要在$x$所有儿子的子树中选出$2 \sim 4$个子树,每一个子树贡献一条从其内部到$x$的路径,使得连通块数量最大.因为我们预处理了$mxlen_x$和$uplen_x$,所以直接把所有这样的值丢到堆里面去取前若干大贡献答案即可.
C.两条路径距离最近的点的距离$\geq 2$:考虑枚举距离最近的两个点的路径上的点$x$,以$x$为树根,那么相当于需要在$x$的所有儿子中选出两条路径使得连通块数量最大.同样预处理了$mxnum_x$和$upnum_x$所以把所有这样的值丢进堆里取前$2$大更新答案.
#include<bits/stdc++.h>
using namespace std;
int read(){
int a = 0; char c = getchar(); bool f = 0;
while(!isdigit(c)){f = c == '-'; c = getchar();}
while(isdigit(c)){
a = a * 10 + c - 48; c = getchar();
}
return f ? -a : a;
}
const int _ = 1e5 + 7;
vector < int > ch[_];
int N , ans , fa[_]; int mxl[_] , mxn[_] , upl[_] , upn[_];
void dfs1(int x){
upl[x] = upn[x] = 0; int cnt = ch[x].size() - (x != 1); mxl[x] = 1; mxn[x] = 2;
for(auto t : ch[x])
if(t != fa[x]){
fa[t] = x; dfs1(t); mxn[x] = max(mxn[x] , max(mxn[t] - cnt + 2 , mxl[x] + mxl[t]));
mxl[x] = max(mxl[x] , mxl[t]);
}
mxl[x] += cnt - 1; mxn[x] += cnt - 2;
}
int p1[_] , p2[_] , s1[_] , s2[_];
void dfs2(int x){
ans = max(ans , upn[x] + mxn[x]);
int cnt = (int)ch[x].size() - 1;
p1[0] = s1[ch[x].size() + 1] = 1; p2[0] = s2[ch[x].size() + 1] = 2;
for(int i = 1 ; i <= ch[x].size() ; ++i){
int t = ch[x][i - 1] , p = t == fa[x] ? upl[x] : mxl[t] , q = t == fa[x] ? upn[x] : mxn[t];
p2[i] = max(p2[i - 1] , max(q - cnt + 2 , p1[i - 1] + p)); p1[i] = max(p1[i - 1] , p);
}
for(int i = ch[x].size() ; i ; --i){
int t = ch[x][i - 1] , p = t == fa[x] ? upl[x] : mxl[t] , q = t == fa[x] ? upn[x] : mxn[t];
s2[i] = max(s2[i + 1] , max(q - cnt + 2 , s1[i + 1] + p)); s1[i] = max(s1[i + 1] , p);
}
for(int i = 1 ; i <= ch[x].size() ; ++i)
if(ch[x][i - 1] != fa[x]){
int t = ch[x][i - 1]; upl[t] = max(p1[i - 1] , s1[i + 1]) + cnt - 1;
upn[t] = max(max(p2[i - 1] , s2[i + 1]) , p1[i - 1] + s1[i + 1]) + cnt - 2;
}
for(int i = 1 ; i <= ch[x].size() ; ++i) if(ch[x][i - 1] != fa[x]) dfs2(ch[x][i - 1]);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
for(int T = read() , x = read() ; T ; --T){
N = read(); for(int i = 1 ; i <= N ; ++i) ch[i].clear();
for(int i = 1 ; i <= 2 * x ; ++i) read();
for(int i = 1 ; i < N ; ++i){
int a = read() , b = read(); ch[a].push_back(b); ch[b].push_back(a);
}
ans = 0; dfs1(1); dfs2(1);
for(int i = 1 ; i <= N ; ++i)
if(ch[i].size() >= 2){
priority_queue < int > q;
for(auto t : ch[i]) q.push(t == fa[i] ? upn[i] : mxn[t]);
int sum = q.top(); q.pop(); sum += q.top(); q.pop();
ans = max(ans , sum + 1); while(!q.empty()) q.pop();
for(auto t : ch[i]) q.push(t == fa[i] ? upl[i] : mxl[t]);
for(int i = 1 ; i <= 4 ; ++i) q.push(1);
sum = 0; for(int i = 1 ; i <= 4 ; ++i){sum += q.top(); q.pop();}
ans = max(ans , sum + (int)ch[i].size() - 4);
}
printf("%d\n" , ans);
}
return 0;
}
分手是祝愿
sol
不难发现对于一个确定的状态,最小步数和对于最小步数需要按的开关集合是固定的,且在摁了某个开关后,不会影响其他开关在最小步数中是否会被摁到.
设开始局面最小步数为$p$,设$f_i(i > K)$表示经过最小步数为$i$的局面的期望次数,那么$f_i = [i \neq K + 1]f_{i-1}\frac{N-i+1}{N} + [i \neq N]f_{i+1}\frac{i+1}{N} + [i == p]$.
把这些所有方程都加起来就可以得到$\frac{K+1}{N}f_{K+1} = 1$,进而可以求得所有的$f_i$.答案就是$\sum\limits_{i=K+1}^Nf_i$.
#include<bits/stdc++.h>
using namespace std;
const int _ = 1e5 + 3;
int N , K , val , jc = 1 , arr[_];
int poww(long long a , int b){
int times = 1;
while(b){
if(b & 1) times = times * a % _;
a = a * a % _; b >>= 1;
}
return times;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
cin >> N >> K; for(int i = 1 ; i <= N ; jc = 1ll * jc * i++ % _) cin >> arr[i];
for(int i = N ; i ; --i)
if(arr[i]){
++val;
for(int j = 1 ; j * j <= i ; ++j)
if(i % j == 0){arr[j] ^= 1; if(i / j != j) arr[i / j] ^= 1;}
}
if(val <= K){printf("%lld\n" , 1ll * val * jc % _); return 0;}
int pre1 = 0 , pre2 = 1ll * N * poww(K + 1 , _ - 2) % _ , ans = pre2;
for(int i = K + 1 ; i < N ; ++i){
int p1 = 1ll * poww(N , _ - 2) * (N - i + 1) % _ , p3 = 1ll * poww(N , _ - 2) * (i + 1) % _;
int now = (pre2 - 1ll * pre1 * p1 % _ + _ - (i == val)) * poww(p3 , _ - 2) % _;
pre1 = pre2; pre2 = now; ans = (ans + now) % _;
}
cout << 1ll * (ans + K) * jc % _; return 0;
}
寿司餐厅
sol
看到选择什么就要选择什么、选择有代价和收益的问题,考虑最大权闭合子图.那么我们只需要考虑每一个点的代价和收益以及限制关系.
首先,选择$d_{i,j}(i
对于$d_{i,i}$,考虑$m = 0$的情况,此时选择第$i$个寿司的收益就是$d_{i,i}-ca_i$,也是相对明确的.
当$m \neq 0$时相当于每一个种类的寿司选择第一个时会有$mx^2$的贡献.考虑对所有种类再建一个点,选择该种类的寿司就必须选择该种类,选择第$x$个种类的代价是$mx^2$.这样寿司的代价就可以明确地表示在图中了,跑最大权闭合子图即可得到答案.
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return f ? -a : a;
}
namespace flow{
const int MAXN = 1e5 + 7 , MAXM = 1e6 + 7;
struct Edge{
int end , upEd , f , c;
}Ed[MAXM];
int head[MAXN];
int N , M , S , T , cntEd = 1;
queue < int > q;
inline void addEd(int a , int b , int c , int d = 0){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
Ed[cntEd].f = c;
Ed[cntEd].c = d;
head[a] = cntEd;
}
inline void addE(int a , int b , int c , int d = 0 , bool f = 0){
addEd(a , b , c , d); addEd(b , a , c * f , -d);
}
int cur[MAXN] , dep[MAXN];
inline bool bfs(){
while(!q.empty())
q.pop();
q.push(S);
memset(dep , 0 , sizeof(dep));
dep[S] = 1;
while(!q.empty()){
int t = q.front();
q.pop();
for(int i = head[t] ; i ; i = Ed[i].upEd)
if(Ed[i].f && !dep[Ed[i].end]){
dep[Ed[i].end] = dep[t] + 1;
if(Ed[i].end == T){
memcpy(cur , head , sizeof(head));
return 1;
}
q.push(Ed[i].end);
}
}
return 0;
}
inline int dfs(int x , int mF){
if(x == T)
return mF;
int sum = 0;
for(int &i = cur[x] ; i ; i = Ed[i].upEd)
if(Ed[i].f && dep[Ed[i].end] == dep[x] + 1){
int t = dfs(Ed[i].end , min(mF - sum , Ed[i].f));
if(t){
Ed[i].f -= t;
Ed[i ^ 1].f += t;
sum += t;
if(sum == mF)
break;
}
}
return sum;
}
int Dinic(int s , int t){
int ans = 0;
S = s; T = t;
while(bfs())
ans += dfs(S , INF);
return ans;
}
}
int A[307] , lsh[307] , ind[307][307] , L , sum;
int main(){
#ifndef ONLINE_JUDGE
freopen("in" , "r" , stdin);
//freopen("out" , "w" , stdout);
#endif
int N = read() , M = read();
for(int i = 1 ; i <= N ; ++i)
lsh[i] = A[i] = read();
sort(lsh + 1 , lsh + N + 1);
L = unique(lsh + 1 , lsh + N + 1) - lsh - 1;
int S = 0 , T = N * (N + 1) / 2 + N + L + 1 , cnt = 0;
for(int i = 1 ; i <= N ; ++i)
for(int j = i ; j <= N ; ++j)
ind[i][j] = ++cnt;
for(int i = 1 ; i <= N ; ++i)
for(int j = i ; j <= N ; ++j){
int val = read();
if(val > 0){
sum += val;
flow::addE(S , ind[i][j] , val);
}
else flow::addE(ind[i][j] , T , -val);
if(i == j)
flow::addE(ind[i][j] , cnt + i , INF);
else{
flow::addE(ind[i][j] , ind[i + 1][j] , INF);
flow::addE(ind[i][j] , ind[i][j - 1] , INF);
}
}
for(int i = 1 ; i <= N ; ++i){
flow::addE(cnt + i , T , A[i]);
flow::addE(cnt + i , cnt + N + lower_bound(lsh + 1 , lsh + L + 1 , A[i]) - lsh , INF);
}
for(int i = 1 ; i <= L ; ++i)
flow::addE(cnt + N + i , T , M * lsh[i] * lsh[i]);
cout << sum - flow::Dinic(S , T);
return 0;
}
原文地址:https://www.cnblogs.com/Itst/p/11518660.html