Preface

最近恰好不知道做什麼題,所以就按老葉要求做上面的比賽。

稍微看了下感覺難度適中,大部分題目偏向聯賽難度,當然也有些題目打到了省選題的感覺(基本都是Div1的題)

這裡就簡單拿一些我做得動的題講一下吧。排列順序按照我做的順序排(不一定按難度


F 爬爬爬山

開的時候先看了一眼榜,發現這題過的人最多,那麼肯定最可做

看了題目想了大概5min才出解吧,我感覺我水題秒的還是不夠快啊

首先容易發現只有所有高度\(>h_1+k\)的山需要降低高度,那麼我們可以直接拆點

入點和出點之前連這座山需要降低的高度的代價(不改就賦為\(0\)),然後無向圖建邊,出點連入點

然後不就是最短路了麼,隨便寫個DJ就過了(乘法沒開long longWA了2發)

CODE

#include<cstdio>
#include<cctype>
#include<queue>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
typedef long long LL;
const int N=100005;
const LL INF=1e18;
struct edge
{
int to,nxt; LL v;
}e[N*5]; int n,m,k,head[N<<1],cnt,h[N],x,y,z;
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
class SSSP
{
private:
struct data
{
int id; LL val;
inline data(CI Id=0,const LL& Val=0)
{
id=Id; val=Val;
}
inline friend bool operator < (const data& A,const data &B)
{
return A.val>B.val;
}
}; priority_queue <data> small; int all; bool vis[N<<1]; LL dis[N<<1];
public:
#define to e[i].to
inline void Dijkstra(void)
{
RI i; for (i=2,all=n<<1;i<=all;++i) dis[i]=INF;
small.push(data(1,0)); while (!small.empty())
{
int now=small.top().id; small.pop();
if (vis[now]) continue; vis[now]=1;
for (i=head[now];i;i=e[i].nxt)
if (!vis[to]&&dis[to]>dis[now]+e[i].v)
small.push(data(to,dis[to]=dis[now]+e[i].v));
}
printf("%lld",dis[all]);
}
#undef to
}G;
inline void add(CI x,CI y,const LL& z)
{
e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
}
inline LL sqr(CI x)
{
return x<0?0:1LL*x*x;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (F.read(n),F.read(m),F.read(k),i=1;i<=n;++i) F.read(h[i]);
for (i=1;i<=n;++i) add(i,n+i,sqr(h[i]-h[1]-k)); for (i=1;i<=m;++i)
F.read(x),F.read(y),F.read(z),add(n+x,y,z),add(n+y,x,z);
return G.Dijkstra(),0;
}

J 奪寶奇兵

其實並不是很適合二開的題目,但是先看到這題感覺不錯就先寫了,然後調一個邊界花了半個多小時

先講一下Div2版本的做法,我們可以列舉所有村民的寶物最大值,然後把大於這個值的村民的物品低價都拿了

然後如果不夠怎麼辦,在剩下的裡面拿小的知道超過這個最大值即可

但是這樣複雜度\(O(nm)\),無法在Div1中通過

那麼我們考慮優化,其實思想還是一樣的,由於物品一共只有\(m\)個,我們可以想象對於每個人的物品按價格從小到大從上到下放置,那麼我們倒序列舉最大值,那些物品是不是一個一層一層被取走的感覺

我們在維護被取走的物品的個數和總價值的同時,由於可能要有補足,所以要動態維護剩下的物品裡的前\(k\)小值和

這個套路吧,一發值域線段樹隨便跑,複雜度\(O(m\log 10^9)\)

這裡Div2的暴力懶得寫了,所以直接上Div的CODE吧

#include<cstdio>
#include<cctype>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
typedef long long LL;
const int N=100005,S=1e9;
int n,m,x,y,mx,cur,lim; LL ans=1e18,ret; vector <int> v[N],p[N];
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
class Segment_Tree
{
private:
static const int P=30;
struct Segment
{
int ch[2],size; LL sum;
}node[N*P]; int tot;
public:
int rt;
#define lc(x) node[x].ch[0]
#define rc(x) node[x].ch[1]
#define Si(x) node[x].size
#define S(x) node[x].sum
inline void modify(int& now,CI val,CI opt,CI l=1,CI r=S)
{
if (!now) now=++tot; Si(now)+=opt; S(now)+=opt*val; if (l==r) return; int mid=l+r>>1;
if (val<=mid) modify(lc(now),val,opt,l,mid); else modify(rc(now),val,opt,mid+1,r);
}
inline LL query(CI now,CI rk,CI l=1,CI r=S)
{
if (!now) return 0; if (l==r) return 1LL*l*rk; int mid=l+r>>1;
return rk<=Si(lc(now))?query(lc(now),rk,l,mid):S(lc(now))+query(rc(now),rk-Si(lc(now)),mid+1,r);
}
#undef lc
#undef rc
#undef Si
#undef S
}T;
inline bool cmp(CI x,CI y)
{
return x>y;
}
inline void maxer(int& x,int y)
{
if (y>x) x=y;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j; for (F.read(n),F.read(m),i=1;i<=m;++i)
F.read(x),F.read(y),v[y].push_back(x),T.modify(T.rt,x,1);
for (i=1;i<=n;++i) sort(v[i].begin(),v[i].end(),cmp),maxer(mx,v[i].size());
for (i=1;i<=n;++i) for (lim=v[i].size(),j=0;j<lim;++j)
p[j+1].push_back(v[i][j]); for (i=mx+1;~i;--i)
{
for (lim=p[i].size(),cur+=lim,j=0;j<lim;++j)
T.modify(T.rt,p[i][j],-1),ret+=p[i][j]; LL cost;
if (cur>=i) cost=ret; else cost=ret+T.query(T.rt,i-cur);
if (cost<ans) ans=cost;
}
return printf("%lld",ans),0;
}

C 拆拆拆數

構造題?首先大力猜測答案不會很大!並順手判掉\(n=1\)的情況

然後我首先想到了\(2\)於所有奇數互質,所以如果\(A,B\)都是奇數那麼肯定可以分成\((A-2,2),(2,B-2)\)的形式

那有了偶數怎麼辦?還是利用上面的結論,由於這裡的數都\(\ge 5\),因此肯定可以分成奇質數+奇數的形式

奇數的話我們還是從\(B\)裡找一個\(2\)和它配對,那麼接下來就是找一個奇質數於\(B-2\)互質了

由於前\(15\)個質數的累計已經超過了\(10^{18}\),所以我們大力列舉即可,複雜度最壞是單次\(O(15\log B)\)的

輸出要注意一下,具體看CODE

#include<cstdio>
#include<cctype>
#define int long long
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int prime[15]={3,5,7,11,13,17,19,23,29,31,37,41,43,47,51};
int t,x,y;
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
#define pc(ch) (Ftop<S?Fout[Ftop++]=ch:(fwrite(Fout,1,S,stdout),Fout[(Ftop=0)++]=ch))
char Fin[S],Fout[S],*A,*B; int Ftop,pt[25];
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
Tp inline void write(T x,const char& ch)
{
if (!x) return (void)(pc('0'),pc(ch)); RI ptop=0;
while (x) pt[++ptop]=x%10,x/=10; while (ptop) pc(pt[ptop--]+48); pc(ch);
}
inline void Fend(void)
{
fwrite(Fout,1,Ftop,stdout);
}
#undef tc
#undef pc
}F;
inline int gcd(CI x,CI y)
{
return y?gcd(y,x%y):x;
}
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (F.read(t);t;--t)
{
F.read(x); F.read(y);
if (gcd(x,y)==1)
{
F.write(1,'\n'); F.write(x,' ');
F.write(y,'\n'); continue;
}
F.write(2,'\n'); if ((x&1)&&(y&1))
{
F.write(2,' '); F.write(y-2,'\n');
F.write(x-2,' '); F.write(2,'\n'); continue;
}
bool flag=0; if (x&1) { int t=x; x=y; y=t; flag=1; }
for (RI i=0;i<15;++i) if (gcd(prime[i],y-2)==1)
{
if (flag)
{
F.write(y-2,' '); F.write(prime[i],'\n');
F.write(2,' '); F.write(x-prime[i],'\n'); break;
} else
{
F.write(prime[i],' '); F.write(y-2,'\n');
F.write(x-prime[i],' '); F.write(2,'\n'); break;
}
}
}
return F.Fend(),0;
}

E 流流流動

這些題目名都是什麼鬼?感覺這題分析成分更大一些。。。

首先那個連邊方式你如果有一定數學基礎的話很容易發現這個轉移方式就是角谷猜想的形式。

乍一看角谷猜想是必然成環的,所以我開始就認為這種成環的東西不好做

不夠如果你仔細讀題的話會發現\(i=1\)的時候是不建邊的,所以角谷猜想的關鍵步驟——以\(1\)為中轉迴圈就被打破了

所以其實這個圖是一個森林的形式,然後就是對每個聯通塊做一個簡單DP即可。

CODE

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
struct edge
{
int to,nxt;
}e[N<<1]; int n,head[N],cnt,a[N],b[N],f[N][2],ans; bool vis[N];
inline void addedge(CI x,CI y)
{
e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
e[++cnt]=(edge){x,head[y]}; head[y]=cnt;
}
inline int min(CI a,CI b)
{
return a<b?a:b;
}
inline int max(CI a,CI b)
{
return a>b?a:b;
}
#define to e[i].to
inline void DP(CI now,CI fa)
{
f[now][0]=0; f[now][1]=a[now]; vis[now]=1;
for (RI i=head[now];i;i=e[i].nxt) if (to!=fa)
{
DP(to,now); f[now][0]+=max(f[to][0],f[to][1]);
f[now][1]+=max(f[to][0],f[to][1]-b[min(now,to)]);
}
}
#undef to
int main()
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (i=1;i<=n;++i) scanf("%d",&b[i]); for (i=2;i<=n;++i)
if (i&1) { if ((3*i+1<=n)) addedge(i,3*i+1); } else addedge(i,i>>1);
for (i=1;i<=n;++i) if (!vis[i]) DP(i,0),ans+=max(f[i][0],f[i][1]);
return printf("%d",ans),0;
}

I 起起落落

比較神的一道題,肝了一個晚上才出來233

首先容易想到一個暴力的DP,令\(f_i\)表示以\(i\)為結尾的答案,那麼顯然有轉移:

\[f_i=\sum_{j<i} [p_j>p_i](f_j+1)\sum_{j<k<i} [p_k<p_i]
\]

這個可以直接往前列舉\(j\)的時候順帶算出\([p_k<p_i]\)的個數,\(O(n^2)\)可以過Div2的範圍

那麼考慮優化,注意到\(p\)其實是一個排列,所以我們可以很方便地用權值線段樹(怎麼又是它)來維護答案

用一個思想,我們在計算貢獻是先把它全部算上去,然後到後面在減去多算的貢獻。

具體地,我們在列舉一個數的時候,先把它當做\(i\)算一遍答案,然後在當作\(z\)給後面的數加上次數,最後在當作\(j\)更新當前答案

這樣只需要寫一個奇怪的線段樹即可維護答案了,複雜度\(O(n\log n)\)

CODE

#include<cstdio>
#include<cctype>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=100005,mod=1e9+7;
int n,x,ans,cur;
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int sum(CI x,CI y)
{
int t=x+y; return t>=mod?t-mod:t;
}
class Segment_Tree
{
private:
struct Segment
{
int base,val,tag;
}node[N<<2];
#define B(x) node[x].base
#define V(x) node[x].val
#define T(x) node[x].tag
inline void pushup(CI now)
{
B(now)=sum(B(now<<1),B(now<<1|1)); V(now)=sum(V(now<<1),V(now<<1|1));
}
inline void pushdown(CI now)
{
if (!T(now)) return; inc(T(now<<1),T(now)); inc(V(now<<1),1LL*T(now)*B(now<<1)%mod);
inc(T(now<<1|1),T(now)); inc(V(now<<1|1),1LL*T(now)*B(now<<1|1)%mod); T(now)=0;
}
public:
#define TN CI now=1,CI l=1,CI r=n
#define O beg,end
inline int queryval(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return V(now); int mid=l+r>>1,ret=0; pushdown(now);
if (beg<=mid) inc(ret,queryval(O,now<<1,l,mid));
if (end>mid) inc(ret,queryval(O,now<<1|1,mid+1,r)); return ret;
}
inline int querybase(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return B(now); int mid=l+r>>1,ret=0; pushdown(now);
if (beg<=mid) inc(ret,querybase(O,now<<1,l,mid));
if (end>mid) inc(ret,querybase(O,now<<1|1,mid+1,r)); return ret;
}
inline void modify(CI beg,CI end,CI val,TN)
{
if (beg<=l&&r<=end) return (void)(inc(T(now),val),inc(V(now),1LL*val*B(now)%mod));
int mid=l+r>>1; pushdown(now); if (beg<=mid) modify(O,val,now<<1,l,mid);
if (end>mid) modify(O,val,now<<1|1,mid+1,r); pushup(now);
}
#undef O
inline void updata(CI pos,CI mb,CI mv,TN)
{
if (l==r) return (void)(inc(B(now),mb),inc(V(now),mv)); int mid=l+r>>1; pushdown(now);
if (pos<=mid) updata(pos,mb,mv,now<<1,l,mid); else updata(pos,mb,mv,now<<1|1,mid+1,r); pushup(now);
}
#undef TN
#undef B
#undef V
#undef T
}T;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (F.read(n),i=1;i<=n;++i)
{
F.read(x); inc(ans,cur=T.queryval(x+1,n)); T.modify(x+1,n,1);
T.updata(x,cur+1,sum(-T.querybase(x+1,n),mod));
}
return printf("%d",ans),0;
}

A 機器人

一個大力分類討論的題,WA了快一頁才過去

其實主要就是兩種大的情況:\(B\)區要走以及\(B\)區不走

不走的情況很簡單,就走一條線段即可,要走的最遠點可以直接搞出來

然後考慮要走的情況,其實肯定就是一個矩形,我們在\(A,B\)的所有點中找到兩個端點即可

但是大坑點來了,如果直接這麼做就會忽略一種情況:出發點在最優矩形的外面!

然後經過一波分析,容易得出直接走到矩形是最優的走法,所以要特判一下

注意各種細節,寫的時候腦子一定要清醒

CODE

#include<cstdio>
#include<cctype>
#include<algorithm>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=100005;
int n,r,m,k,s,p[2][N],cnt[2],sp[N],cnt_sp,x,y,mi=1e9,mx,pmi,pmx;
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
inline void miner(int& x,CI y)
{
if (y<x) x=y;
}
inline void maxer(int& x,CI y)
{
if (y>x) x=y;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (F.read(n),F.read(r),F.read(m),F.read(k),F.read(s),i=1;i<=r;++i)
F.read(x),F.read(y),p[y][++cnt[y]]=x; sp[1]=1; sp[cnt_sp=2]=n;
for (i=1;i<=m;++i) F.read(x),sp[++cnt_sp]=x;
sort(sp+1,sp+cnt_sp+1); cnt_sp=unique(sp+1,sp+cnt_sp+1)-sp-1;
sort(p[0]+1,p[0]+cnt[0]+1); sort(p[1]+1,p[1]+cnt[1]+1);
if (cnt[0]) miner(mi,p[0][1]),maxer(mx,p[0][cnt[0]]);
if (cnt[1]) miner(mi,p[1][1]),maxer(mx,p[1][cnt[1]]);
pmx=lower_bound(sp+1,sp+cnt_sp+1,mx)-sp; pmx=sp[pmx];
pmi=upper_bound(sp+1,sp+cnt_sp+1,mi)-sp-1; pmi=sp[pmi];
if (!cnt[1])
{
if (mx<=s) pmx=s; if (mi>=s) pmi=s;
return printf("%d",pmx-pmi<<1),0;
}
int cur=(pmx-pmi<<1)+(k<<1);
if (s<pmi) cur+=(pmi-s<<1); if (s>pmx) cur+=(s-pmx<<1);
return printf("%d",cur),0;
}

B 吃豆豆(only for Div2)

我太菜了所以只會這題Div2的做法,Div1的感覺可以大小步DP,總之肯定有什麼規律

Div2的話由於\(c\le 1018\),所以我們直接大力DP,以顏色為狀態轉移感覺不是很好策,所以我們以時間為狀態

令\(f_{i,j,k}\)表示時間\(k\)在\(i,j\)時最多能得到多少糖果,轉移的話分四個方向移動和不動轉移一下即可

一邊打麻將一邊寫的,隨便搞了搞也過了233

CODE

#include<cstdio>
#include<cctype>
#include<cstring>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=15,C=1100;
int n,m,c,t[N][N],f[N][N][N*C],sx,sy,tx,ty,ans;
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
inline void maxer(int& x,CI y)
{
if (y>x) x=y;
}
inline void miner(int& x,CI y)
{
if (y<x) x=y;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j,k; for (F.read(n),F.read(m),F.read(c),i=1;i<=n;++i)
for (j=1;j<=m;++j) F.read(t[i][j]); F.read(sx); F.read(sy);
F.read(tx); F.read(ty); memset(f,167,sizeof(f));
for (f[sx][sy][0]=0,k=0;k<=10*c;++k) for (i=1;i<=n;++i) for (j=1;j<=m;++j)
{
if (i-1>=1) maxer(f[i-1][j][k+1],f[i][j][k]+((k+1)%t[i-1][j]?0:1));
if (i+1<=n) maxer(f[i+1][j][k+1],f[i][j][k]+((k+1)%t[i+1][j]?0:1));
if (j-1>=1) maxer(f[i][j-1][k+1],f[i][j][k]+((k+1)%t[i][j-1]?0:1));
if (j+1<=m) maxer(f[i][j+1][k+1],f[i][j][k]+((k+1)%t[i][j+1]?0:1));
maxer(f[i][j][k+1],f[i][j][k]+((k+1)%t[i][j]?0:1));
}
for (k=0;k<=10*c;++k) if (f[tx][ty][k]>=c) { ans=k; break; }
return printf("%d",ans),0;
}

Postscript

據說映象賽Rank前10就送T恤,感覺如果現場打的話可能可以切出\(4,5\)題吧

所以就可以到Div1騙衣服了?感覺這比賽沒什麼人打啊,有些題策不太動啊