国家集训队题选做。
开的新坑。
目前在做IOI2020集训队作业。
CF505E Mr. Kitayuta Vs. Bamboos
我们考虑直接进行二分答案,发现如果我们直接正向模拟并不好做,下取\(0\)的操作是很不方便的。
我们考虑从后往前做,转换为最初为\(n\)个\(H\)物品,每次都减去\(a_i\),每一轮可以选择\(k\)个物品加上\(p\),途中不允许出现\(h_i < 0\),问是否能使所有物品最后高度大于最开始的高度。
每次都贪心进行:
每次选择最有可能出现\(h_i < 0\)的位置加。
如果不可能出现则直接判断能否满足所有物品最后高度大于最开始的高度
CF505E
#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
#define N 100005
ll n,m,k,p;
ll h[N],a[N],c[N];
struct P{int rest,id;};
bool operator < (P a,P b){
return a.rest > b.rest;
}
std::priority_queue<P>QWQ;
std::queue<P>QAQ;
inline ll check(ll H){
for(int i = 1;i <= n;++i)
c[i] = 0;
while(QWQ.size())
QWQ.pop();
for(int i = 1;i <= n;++i)
if(H - a[i] * m < h[i])
QWQ.push((P){H / a[i],i});
for(int i = 1;i <= m;++i){//还剩多少天
for(int j = 1;j <= k;++j){
if(QWQ.empty())
break;
P u = QWQ.top();QWQ.pop();
if(u.rest < i)
return 0;
++c[u.id];
if(H + c[u.id] * p - a[u.id] * m < h[u.id])
QWQ.push((P){(H + c[u.id] * p) / a[u.id],u.id});
}
}
return QWQ.empty();
}
int main(){
scanf("%lld%lld%lld%lld",&n,&m,&k,&p);
for(int i = 1;i <= n;++i)
scanf("%lld%lld",&h[i],&a[i]);
ll l = 0,r = 1e18;
#define mid ((l + r) >> 1)
while(l + 1 < r){
if(check(mid))
r = mid - 1;
else
l = mid + 1;
}
l += 100;
while(check(l - 1))
l -- ;
std::cout<<l<<std::endl;
}
CF526F Pudding Monsters
压扁到序列上,考虑到就变成有多少个\(k\)长的序列是连续序列,于是我们移动右指针加上单调栈维护,在线段树上维护\(max - min - len\)的值。
CF538H
#include<iostream>
#include<cstdio>
#define ll long long
#define N 300005
ll sx[N],sn[N],stx,stn;
ll n;
ll a[N];
struct P{ll cnt,v,tag;}e[N << 2];
#define l(x) (x << 1)
#define r(x) (x << 1 | 1)
#define mid ((l + r) >> 1)
//mx - mi - (r - l) = 0
inline void build(ll u,ll l,ll r){
e[u].cnt = r - l + 1;
e[u].tag = 0;
if(l == r)
return ;
build(l(u),l,mid);
build(r(u),mid + 1,r);
}
ll ans;
inline void upd(ll u,ll l,ll r,ll tl,ll tr,ll p){
// std::cout<<u<<" "<<tl<<" "<<tr<<" "<<p<<" "<<std::endl;
if(tl <= l && r <= tr){
e[u].v += p;
e[u].tag += p;
return ;
}
if(e[u].tag){
e[l(u)].v += e[u].tag;
e[l(u)].tag += e[u].tag;
e[r(u)].v += e[u].tag;
e[r(u)].tag += e[u].tag;
e[u].tag = 0;
}
if(tl <= mid)
upd(l(u),l,mid,tl,tr,p);
if(tr > mid)
upd(r(u),mid + 1,r,tl,tr,p);
e[u].v = std::min(e[r(u)].v,e[l(u)].v);
e[u].cnt = (e[l(u)].v == e[u].v ? e[l(u)].cnt : 0) + (e[r(u)].v == e[u].v ? e[r(u)].cnt : 0);
}
int main(){
scanf("%lld",&n);
for(int i = 1;i <= n;++i){
ll x,y;
scanf("%lld%lld",&x,&y);
a[x] = y;
}
build(1,1,n);
for(int i = 1;i <= n;++i){
while(stx && a[sx[stx]] < a[i])upd(1,1,n,sx[stx - 1] + 1,sx[stx],-a[sx[stx]]),--stx;
while(stn && a[sn[stn]] > a[i])upd(1,1,n,sn[stn - 1] + 1,sn[stn],a[sn[stn]]),--stn;
upd(1,1,n,1,i,-1),sx[++stx] = sn[++stn] = i;
upd(1,1,n,sx[stx - 1] + 1,i,a[i]),upd(1,1,n,sn[stn - 1] + 1,i,-a[i]);
ans += e[1].cnt;
}
std::cout<<ans<<std::endl;
}
CF538H Summer Dichotomy
(来自自己某次讲课的\(ppt\))
CF526F
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <queue>
#include <vector>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int T,t,n,m,l[100050],r[100050],n1,n2,ans[100050];
vector <int> G[100050];
inline void dfs(int u,int col)
{
if (ans[u])
{
if (ans[u]!=col)
{
puts("IMPOSSIBLE");
exit(0);
}
return;
}
ans[u]=col;
for (int i=0;i<G[u].size();i++)
{
int v=G[u][i];
dfs(v,3-col);
}
}
int main()
{
t=read(),T=read();
n=read(),m=read();
n1=1<<30,n2=-(1<<30);
for (int i=1;i<=n;i++)
{
l[i]=read();
r[i]=read();
}
for (int i=1;i<=n;i++)
n1=min(n1,r[i]);
for (int i=1;i<=n;i++)
n2=max(n2,l[i]);
if (n1+n2<t)
n2=t-n1;
else if (n1+n2>T)
n1=T-n2;
if (n1<0 || n2<0)
{
puts("IMPOSSIBLE");
return 0;
}
for (int i=1;i<=m;i++)
{
int u=read(),v=read();
G[u].push_back(v);
G[v].push_back(u);
}
for (int i=1;i<=n;i++)
{
if (l[i]<=n1 && n1<=r[i] && !(l[i]<=n2 && n2<=r[i]))
dfs(i,1);
else if (l[i]<=n2 && n2<=r[i] && !(l[i]<=n1 && n1<=r[i]))
dfs(i,2);
else if (!(l[i]<=n2 && n2<=r[i]) && !(l[i]<=n1 && n1<=r[i]))
{
puts("IMPOSSIBLE");
return 0;
}
}
for (int i=1;i<=n;i++)
if (!ans[i])
dfs(i,1);
puts("POSSIBLE");
cout << n1 << " " << n2 << endl;
for (int i=1;i<=n;i++)
cout << ans[i];
return 0;
}
CF576D Flights for Regular Customers
考虑按限制对边排序,并对经过\(t\)条边可以到达的点进行多源\(BFS\),再加上\(t\)可以求出最短路。
对于前者,可以使用矩阵优化。
由于只有\(0,1\)元素,可以考虑使用\(bitset\).
代码暂鸽抱歉。
CF512D Fox And Travelling
考虑到环是一定不能拿出任何一个点的。
所以我们把环去掉即可。
然后在树上做,注意到有有根树和无根树的差别。
前者根为与环相连的点。
计\(f_{i,j}\)为子树的合法方案,依次向上合并。
注意到无根树我们要枚举根节点,最后要对整个联通块求出一个答案。由于是子树,所以一个根的\(f_{i,j}\)会被\(S - j\)个其他点在子树统计时也统计到,所以整个联通块答案要除\(S - j\)。
然后把这些联通块的答案合并到最终答案。
CF512D
#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#define mod 1000000009
#define ll long long
#define N 1005
std::vector<int>in[N];
ll n,m;
ll f[N][N],w[N],s[N],b[N],d[N];
std::queue<int>q;
inline void dfs(int x,int o,long long &si){
b[x] = o,++si;
for(int i = 0;i < in[x].size();++i){
int v = in[x][i];
if(!d[v] && !b[v])dfs(v,o,si);
}
}
ll p[N << 2],inv[N << 2];
inline ll pow(ll a,ll b){
if(a == 0)return 1;
ll ans = 1;
while(b){
if(b & 1)ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
inline ll C(ll x,ll y){
if(x < y || x < 0 || y < 0)return 0;
return p[x] * inv[y] % mod * inv[x - y] % mod;
}
inline void dp(int x,int fa){
s[x] = 1,f[x][0] = 1;
for(int i = 0;i < in[x].size();++i){
int v = in[x][i];
if(b[v] != b[x] || v == fa)continue;
dp(v,x);
for(int j = 0;j < s[v];++j)f[x][s[x] + j] = 0;
for(int j = s[x] - 1;j >= 0;--j)
for(int k = 1;k <= s[v];++k)
f[x][j + k] = (f[x][j + k] + f[x][j] * f[v][k] % mod * C(j + k,j) % mod) % mod;
s[x] += s[v];
}
f[x][s[x]] = f[x][s[x] - 1];
}
inline void get(int x){
dp(x,0);
for(int i = 0;i <= s[x];++i)
f[0][i] = (f[0][i] + f[x][i]) % mod;
}
ll ans[N],S;
int main(){
scanf("%lld%lld",&n,&m);
p[0] = 1;
for(int i = 1;i <= 300;++i)
p[i] = p[i - 1] * i % mod;
inv[300] = pow(p[300],mod - 2);
for(int i = 299;i >= 0;--i)
inv[i] = (i + 1) * inv[i + 1] % mod;
for(int i = 1;i <= m;++i){
ll x,y;
scanf("%lld%lld",&x,&y);
in[x].push_back(y);
in[y].push_back(x);
d[x] ++ ,d[y] ++ ;
}
for(int i = 1;i <= n;++i)if(d[i] <= 1) w[i] = 1,q.push(i);
while(q.size()){
int u = q.front();q.pop();
for(int i = 0;i < in[u].size();++i){
int v = in[u][i];
if(--d[v] <= 1 && !w[v])w[v] = 1,q.push(v);
}
}
for(int i = 1;i <= n;++i)if(d[i] == 1)dfs(i,i,s[i]);//find_dir_tree
for(int i = 1;i <= n;++i)if(!d[i] && !b[i])dfs(i,i,s[i]);
ans[0] = 1;
for(int i = 1;i <= n;++i){
if(i == b[i]){//is father
int o = s[i];
if(d[i] == 1)get(i);
else{
for(int j = 1;j <= n;++j)
if(b[j] == i)get(j);
for(int j = 0;j <= o;++j)f[0][j] = (f[0][j] * pow(o - j,mod - 2)) % mod;
}
for(int j = S;j >= 0;--j)
for(int k = 1;k <= o;++k)
ans[j + k] = (ans[j + k] + ans[j] * f[0][k] % mod * C(j + k,j) % mod) % mod;
for(int j = 0;j <= o;++j)f[0][j] = 0;
S += o;
}
}
for(int i = 0;i <= n;++i)
std::cout<<ans[i]<<std::endl;
}
CF521D Shop
原本读错题。翻译背锅。
考虑赋值只会进行一次,我们选择最大的,把他转变为加法。
再考虑从大到小把所有加法变成乘法,发现这样分数递增保证了正确性。
最后选择\(k\)个最大的乘法,按赋值加法乘法的顺序输出。
CF521D
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
#define N ((ll)1e5 + 10)
ll n,m,k;
ll cnt1,cnt2,cnt3,ans;
struct P{ll x,y,id,from,ansid;}m1[N],m2[N],m3[N],fans[N];//赋值 加 乘
bool operator < (P a,P b){return a.x * b.y > b.x * a.y;}
bool work[N];
ll num[N];
inline void del1(){
std::sort(m1 + 1,m1 + cnt1 + 1);
for(int i = 1;i <= cnt1;++i){
P a = m1[i];
if(!work[a.id]){
if(a.x >= num[a.id]){
a.x = a.x - num[a.id];
m2[++cnt2] = a;
}
work[a.id] = 1;
}
}
}
inline void del2(){
std::sort(m2 + 1,m2 + cnt2 + 1);
for(int i = 1;i <= cnt2;++i){
P a = m2[i];
if(a.x > 0){
ll to = num[a.id] + a.x;
a.x = to,a.y = num[a.id];
m3[++cnt3] = a;
num[a.id] = to;
}
}
}
bool cmp(P a,P b){return a.from < b.from;}
inline void del3(){
std::sort(m3 + 1,m3 + cnt3 + 1);
for(int i = 1;i <= std::min(k,cnt3);++i)
fans[++ans] = m3[i];
std::sort(fans + 1,fans + ans + 1,cmp);
}
int main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i = 1;i <= n;++i)
scanf("%lld",&num[i]);
for(int i = 1;i <= m;++i){
ll t,id,b;
scanf("%lld%lld%lld",&t,&id,&b);
if(t == 1)
m1[++cnt1].x = b,m1[cnt1].y = 1,m1[cnt1].from = 1,m1[cnt1].id = id,m1[cnt1].ansid = i;
if(t == 2)
m2[++cnt2].x = b,m2[cnt2].y = 1,m2[cnt2].from = 2,m2[cnt2].id = id,m2[cnt2].ansid = i;
if(t == 3)
m3[++cnt3].x = b,m3[cnt3].y = 1,m3[cnt3].from = 3,m3[cnt3].id = id,m3[cnt3].ansid = i;
}
del1();
del2();
del3();
std::cout<<ans<<std::endl;
for(int i = 1;i <= ans;++i)
std::cout<<fans[i].ansid<<" ";
}
CF547D Mike and Fish
想到了经典的二分图模型,没有想到欧拉回路。
然后不是很理解欧拉回路做法的“奇点只有偶数个的结论”
我们思考把相同横坐标和相同纵坐标的点两两相连,若有剩余则不管。
可以证明连出来的图即一个环的边一定是行,列,行,列这样的形式,为二分图。
我们对其二分染色,则染色颜色为答案。
CF547D
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
#define N 200005
ll n;
struct P{
int x,y,id;
}e[N];
bool cmp (P a,P b){
return a.x < b.x;
}
bool cmp1 (P a,P b){
return a.y < b.y;
}
ll head[N],cnt;
struct E{
int to,next;
}v[N * 10];
inline void add(int x,int y){
v[++cnt].to = y;
v[cnt].next = head[x];
head[x] = cnt;
}
ll c[N];
inline void dfs(int u,int col){
c[u] = col;
for(int i = head[u];i;i = v[i].next){
int vi = v[i].to;
if(c[vi])continue;
dfs(vi,3 - col);
}
}
ll lastx[N],lasty[N];
int main(){
scanf("%lld",&n);
for(int i = 1;i <= n;++i){
scanf("%lld%lld",&e[i].x,&e[i].y);
e[i].id = i;
if(lastx[e[i].x]){
add(lastx[e[i].x],i);
add(i,lastx[e[i].x]);
lastx[e[i].x] = 0;
}else
lastx[e[i].x] = i;
if(lasty[e[i].y]){
add(lasty[e[i].y],i);
add(i,lasty[e[i].y]);
lasty[e[i].y] = 0;
}else
lasty[e[i].y] = i;
}
for(int i = 1;i <= n;++i){
if(!c[i]){
dfs(i,1);
}
}
for(int i = 1;i <= n;++i){
if(c[i] == 1)
putchar('b');
else
putchar('r');
}
}
CF547E Mike and Friends
算是接触了知识盲区。
AC自动机真的好久没碰了。
区间询问改成差分的技巧也忘了。
复习一下。
把所有串建AC自动机。
把区间询问差分。
依次加入串,把他们在AC自动机上的点加1.
然后一次操作相当于查询s[k]的结束节点在fail树上子树和。
CF547E
#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
#define ll long long
#define N (ll)6e5 + 7
#define M 26
#define Q (ll)5e5 + 7
ll n,m,q;
ll l[N],r[N];
char a[N];
ll t[N][M],cnt;
ll ends[N];
inline void ins(int k){
int u = 0;
for(int i = l[k];i <= r[k];++i){
if(!t[u][a[i] - 'a'])
t[u][a[i] - 'a'] = ++cnt;
u = t[u][a[i] - 'a'];
}
ends[k] = u;
}
std::queue<int>QWQ;
ll fail[N];
inline void got_fail(){
for(int i = 0;i < 26;++i)
if(t[0][i])fail[t[0][i]] = 0,QWQ.push(t[0][i]);
while(!QWQ.empty()){
ll u = QWQ.front();QWQ.pop();
for(int i = 0;i < 26;++i){
if(t[u][i])fail[t[u][i]] = t[fail[u]][i],QWQ.push(t[u][i]);
else
t[u][i] = t[fail[u]][i];
}
}
}
ll head[N],tcnt;
struct P{
int to,next;
}e[N];
inline void add(int x,int y){
e[++tcnt].to = y;
e[tcnt].next = head[x];
head[x] = tcnt;
}
ll dfn[N],end[N];
ll dfncnt;
inline void dfs(int u){
dfn[u] = ++dfncnt;
end[u] = dfn[u];
for(int i = head[u];i;i = e[i].next){
int v = e[i].to;
dfs(v);
end[u] = std::max(end[v],end[u]);
}
}
struct Qi{
int op,id,k;
};
std::vector<Qi>QAQ[N];
#define lowbit(x) (x & -x)
ll T[N];
inline void Add(int x){
for(int i = x;i <= N;i += lowbit(i))
T[i] += 1;
}
inline ll find(int x){
ll ans = 0;
for(int i = x;i;i -= lowbit(i))
ans += T[i];
return ans;
}
ll ans[N];
inline void del(int x){
int u = 0;
for(int i = l[x];i <= r[x];++i){
u = t[u][a[i] - 'a'];
Add(dfn[u]);
}
for(int i = 0;i < QAQ[x].size();++i){
ans[QAQ[x][i].id] += QAQ[x][i].op * (find(end[ends[QAQ[x][i].k]]) - find(dfn[ends[QAQ[x][i].k]] - 1));
}
}
int main(){
scanf("%lld%lld",&n,&q);
l[1] = 1;
for(int i = 1;i <= n;++i){
scanf("%s",a + l[i]);
ll len = std::strlen(a + l[i]);
r[i] = l[i] + len - 1;
l[i + 1] = r[i] + 1;
ins(i);
}
got_fail();
for(int i = 1;i <= cnt;++i)
add(fail[i],i);
dfs(0);
for(int i = 1;i <= q;++i){
ll x,y,z;
scanf("%lld%lld%lld",&y,&z,&x);
Qi a;
a.op = -1,a.id = i,a.k = x;
QAQ[y - 1].push_back(a);
a.op = 1,a.id = i,a.k = x;
QAQ[z].push_back(a);
}
for(int i = 1;i <= n;++i){
del(i);
}
for(int i = 1;i <= q;++i)
std::cout<<ans[i]<<std::endl;
}
CF555E Case of Computer Network
考虑我们先边双缩点,边双中能够满足以下条件:
对于任意对点对,都可以调整无向边方向为有向,保证有\(s_i \to t_i\)都成立。
那么我们只要考虑这个跨联通块的就行了。
相当于我们重建一颗树,一次操作为区间定向,判断合法性。
可以用树上差分解决。
CF559E Gerald and Path
感觉这类每个地方操作,求最大值,最小值的题,dp是一种通法。
思考怎么进行dp。
我们设\(f_{i,j,k}\)为考虑到第\(i\)个线段,最右端为\(j\),方向为\(p\)。
那么我们枚举从前面哪一个开始,并用\(O(n)\)来更新dp。
考虑我们可能会有前面的一段的最右端超过这条线段的情形,所以如此设计dp。