2023暑假模拟赛
2023.7.10#
T1#
题面
在一个迷宫中有一个蛋糕。作为一个吃货,Luna 非常想吃到这块蛋糕。现在 Luna 手里有这个迷宫的地图,该地图是一个
#
表示这里是墙砖,不能通过;
.
表示这里是空地,可以通过;
S
表示Luna 的初始位置;
C
表示蛋糕的位置。
Luna 只能在空地上行走,并且只能从一个格子走到与这个格子有公共边的相邻格子上。另外,整个地图的四周都是被墙砖所环绕的。
为了能更快吃到蛋糕,Luna 不知从哪里获得了一把次元枪,这把枪可以用来制造传送门。该枪的使用说明如下:
-
在任何时候,Luna 可以选择上下左右四个方向中的一个开一枪。当她向一个方向开枪之后,子弹会撞到这个方向上第一个遇到的墙砖,并在这个墙砖面向她的面上开启一个传送门。
-
由于能量限制,在同一时刻最多存在两个传送门。如果已经存在两个传送门,那么当Luna 再开枪时,其中一个传送门会被回收用于补充能量,回收哪一个由Luna 自己决定。当Luna 向某个传送门开枪时,会有一个新的传送门取代它,即在一块墙砖的一个面上同时只能存在一个传送门。
-
当存在两个传送门时,Luna 可以进入其中任意一个传送门,然后会从另外一个传送门走出。
由于 Luna 枪法一流,所以开枪是不耗时的。Luna 从一个格子走到任意一个相邻格子的耗时为 1,穿越传送门的耗时也为 1。
现在 Luna 把地图给了你,她想知道她吃到蛋糕的最少耗时是多少,你不忍心拒绝这样一位少女的请求,所以你绞尽脑汁也要把这个问题解决。
对于 10% 的数据,
对于 30% 的数据,
对于另外 20% 的数据,每个空地至少一个墙砖与它相邻。
对于 70% 的数据,
对于 100% 的数据,
sol
最短路。
预处理出每个点走到上,下,左,右的最远点。
走到
#pragma GCC optimize("O2")
#include<queue>
#include<cstdio>
#include<utility>
#include<algorithm>
using namespace std;
const int M=1010,inf=1e9+7;
int n,m; int id(int x,int y){return (x-1)*m+y;}
int sx,sy,ex,ey;
char mp[M][M];
const int dx[]={-1,0,1,0},dy[]={0,-1,0,1};
typedef pair<int,int> PII;
PII to[M][M][4];
#define qwq make_pair
#define nino first
#define miku second
void pre(){
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) if(mp[i][j]!='#'){
to[i][j][0]=(mp[i-1][j]=='#')?qwq(i,j):to[i-1][j][0];
to[i][j][1]=(mp[i][j-1]=='#')?qwq(i,j):to[i][j-1][1];
}
for(int i=n;i>=1;i--)
for(int j=m;j>=1;j--) if(mp[i][j]!='#'){
to[i][j][2]=(mp[i+1][j]=='#')?qwq(i,j):to[i+1][j][2];
to[i][j][3]=(mp[i][j+1]=='#')?qwq(i,j):to[i][j+1][3];
}
}
int dis[M][M]; queue<PII>q;
bool inq[M][M];
int dist(PII u,PII v){
return abs(u.nino-v.nino)+abs(u.miku-v.miku);
}
#define D(x) dis[x.first][x.second]
#define Q(x) inq[x.first][x.second]
#define MP(x) mp[x.first][x.second]
void SPFA(){
for(int i=0;i<M;i++)
for(int j=0;j<M;j++) dis[i][j]=inf;
dis[sx][sy]=0; q.push(qwq(sx,sy)),inq[sx][sy]=true;
while(!q.empty()){
PII u=q.front(); q.pop(),Q(u)=false;
//printf("u=(%d,%d)\n",u.nino,u.miku);
int mndis=inf;
for(int dir=0;dir<4;dir++){
PII v=to[u.nino][u.miku][dir];
if(mndis>dist(u,v)) mndis=dist(u,v);
v=qwq(u.nino+dx[dir],u.miku+dy[dir]);
if(MP(v)=='#') continue;
//printf("(%d,%d)->(%d,%d) dis=1\n",u.nino,u.miku,v.nino,v.miku);
if(D(v)>D(u)+1){
D(v)=D(u)+1;
if(!Q(v)) q.push(v),Q(v)=true;
}
}
for(int dir=0;dir<4;dir++){
PII v=to[u.nino][u.miku][dir];
//printf("(%d,%d)->(%d,%d) dis=%d\n",u.nino,u.miku,v.nino,v.miku,mndis+1);
if(D(v)>D(u)+mndis+1){
D(v)=D(u)+mndis+1;
if(!Q(v)) Q(v)=true,q.push(v);
}
}
}
}
int main(){
freopen("portals.in","r",stdin);
freopen("portals.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=0;i<=m+1;i++) mp[0][i]=mp[n+1][i]='#';
for(int i=1;i<=n;i++) scanf(" %s",mp[i]+1),mp[i][0]=mp[i][m+1]='#';
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(mp[i][j]=='S') sx=i,sy=j;
else if(mp[i][j]=='C') ex=i,ey=j;
pre(),SPFA();
printf("%d",dis[ex][ey]);
return 0;
}
T2#
题面
在2036 年的欧洲,老龄化现象已经渗透到了这个社会的各个角落,随之而来的是严重的健康问题。在欧洲有关部门的建议下,老年人被派遣去递送(也是给老年人的)信件。这项建议将在全欧洲得以实施。
有关部门将欧洲划分为若干个邮区。邮区是一个由双向街道和交叉路口组成的网络,每个区内都可以雇佣任意多的老年人。每天早晨,邮递员接到包裹并按一定路线投送。路线需要满足以下要求:
-
路线的起点和终点相同;
-
同一个路口不能经过两次(体谅老年人);
-
每条街道必须被恰好一条路线覆盖(体谅老年人)。
希望你帮助有关部门求出可行的路线分配方案。
对于 35% 的数据,
对于 65% 的数据,
对于 100% 的数据,
sol
先找到一条不走任何重复边的最长路径。根据题意从
若有
由题意,每个节点度数都是偶数,所以最后刚好删完。
用栈代替了 dfs 以防递归爆栈。
#include<cstdio>
#include<cstring>
const int M=5e5+10;
bool mark[M<<1];
int head[M],cur[M],cnte=1;
struct Edge{int to,next;}e[M<<1];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
int n,m;
int stk[M],top;
int ord[M<<1],top0;
bool vis[M];
int main(){
freopen("postmen.in","r",stdin);
freopen("postmen.out","w",stdout);
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;i++)
scanf("%d%d",&u,&v),add(u,v),add(v,u);
for(int i=1;i<=n;i++) cur[i]=head[i];
stk[++top]=1;
while(top>0){
int u=stk[top],t=cur[stk[top]];
if(t==-1){
ord[++top0]=u; top--;
continue;
}
cur[u]=e[cur[u]].next;
if(mark[t]) continue;
mark[t]=mark[t^1]=true;
stk[++top]=e[t].to;
}
for(int i=1;i<=top0;i++){
int cur=ord[i];
if(vis[cur]){
int tmp;
do{
tmp=stk[top--];
printf("%d ",tmp),vis[tmp]=false;
}while(tmp!=cur);
putchar('\n');
}
stk[++top]=cur,vis[cur]=true;
}
return 0;
}
T3#
题面
有
用
输入时将每个串用两个整数
对于 30% 的数据,
对于 100% 的数据,
sol
先考虑两个数的情况。
将
将所有数排序去重,然后设
到此并没有分类完,事实上还有
所以考虑枚举
#include<cstdio>
#include<algorithm>
const int M=1e5+10,inf=1e9+7;
struct node{
int f,s,cnt;
bool operator<(const node&o)const{return f==o.f?s<o.s:f<o.f;}
}v[M];
int n,m,mx;
int pcnt[2][M],pmax[2][M],pmin[2][M],pcmx[2][M],pcmn[2][M];
int scnt[2][M],smax[2][M],smin[2][M],scmx[2][M],scmn[2][M];
int main(){
freopen("hamming.in","r",stdin);
freopen("hamming.out","w",stdout);
for(int i=0;i<2;i++)
for(int j=0;j<M;j++) pmin[i][j]=smax[i][j]=inf;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d",&v[i].s,&v[i].f),v[i].cnt=1;
std::sort(v+1,v+1+m); int tot=0;
for(int i=1;i<=m;i++)
if(v[i].f!=v[i-1].f||v[i].s!=v[i-1].s||i==1) v[++tot]=v[i];
else v[tot].cnt+=v[i].cnt;
m=tot; //printf("m=%d\n",m);
for(int i=1;i<=m;i++){
int col=v[i].s;
pcnt[col][i]=pcnt[col][i-1]+v[i].cnt,pcnt[col^1][i]=pcnt[col^1][i-1];
pmax[col][i]=v[i].f,pmax[col^1][i]=pmax[col^1][i-1];
pcmx[col][i]=v[i].cnt,pcmx[col^1][i]=pcmx[col^1][i-1];
//pmin[col][i]=std::min(pmin[col][i-1],v[i].f),pmin[col^1][i]=pmin[col^1][i-1];
if(pmin[col][i-1]==inf)
pmin[col][i]=v[i].f,pcmn[col][i]=v[i].cnt;
else
pmin[col][i]=pmin[col][i-1],pcmn[col][i]=pcmn[col][i-1];
pmin[col^1][i]=pmin[col^1][i-1],pcmn[col^1][i]=pcmn[col^1][i-1];
}
for(int i=m;i>=1;i--){
int col=v[i].s;
scnt[col][i]=scnt[col][i+1]+v[i].cnt,scnt[col^1][i]=scnt[col^1][i+1];
smin[col][i]=v[i].f,smin[col^1][i]=smin[col^1][i+1];
scmn[col][i]=v[i].cnt,scmn[col^1][i]=scmn[col^1][i+1];
//smax[col][i]=std::max(smax[col][i+1],v[i].f),smax[col^1][i]=smax[col^1][i+1];
if(smax[col][i+1]==inf)
smax[col][i]=v[i].f,scmx[col][i]=v[i].cnt;
else
smax[col][i]=smax[col][i+1],scmx[col][i]=scmx[col][i+1];
smax[col^1][i]=smax[col^1][i+1],scmx[col^1][i]=scmx[col^1][i+1];
}
long long cnt=0; int ans=0;
for(int b=1;b<=m;b++){
int col=v[b].s;
//(fa,sa)<(fb,sb)<(fc,sc)
//(0,0,0),(1,1,1)
if(pcnt[col][b-1]&&scnt[col][b+1]){
//printf("case 1!\n");
int tmp=2*(smax[col][b+1]-pmin[col][b-1]);
if(tmp>ans) ans=tmp,cnt=0;
if(tmp==ans) cnt+=1ll*pcmn[col][b-1]*v[b].cnt*scmx[col][b+1];
}
//(1,0,0),(0,1,1)
if(pcnt[col^1][b-1]&&scnt[col][b+1]){
//printf("case 2!\n");
int tmp=2*(n-v[b].f+pmax[col^1][b-1]);
if(tmp>ans) ans=tmp,cnt=0;
if(tmp==ans) cnt+=1ll*pcmx[col^1][b-1]*v[b].cnt*scnt[col][b+1];
}
//(0,0,1)(1,1,0)
if(pcnt[col][b-1]&&scnt[col^1][b+1]){
//printf("case 3!\n");
int tmp=2*(n+v[b].f-smin[col^1][b+1]);
if(tmp>ans) ans=tmp,cnt=0;
if(tmp==ans) cnt+=1ll*pcnt[col][b-1]*v[b].cnt*scmn[col^1][b+1];
}
//(0,1,0),(1,0,1)
if(pcnt[col^1][b-1]&&scnt[col^1][b+1]){
//printf("case 4!\n");
int tmp=2*n;
if(tmp>ans) ans=tmp,cnt=0;
if(tmp==ans) cnt+=1ll*pcnt[col^1][b-1]*v[b].cnt*scnt[col^1][b+1];
}
//(fa,sa)<(fb,sb)=(fc,sc)
//(0,0),(1,1)
if(pcnt[col][b-1]){
//printf("case 5!\n");
int tmp=2*(v[b].f-pmin[col][b-1]);
if(tmp>ans) ans=tmp,cnt=0;
if(tmp==ans) cnt+=1ll*pcmn[col][b-1]*v[b].cnt*(v[b].cnt-1)/2;
}
//(1,0),(0,1)
if(pcnt[col^1][b-1]){
//printf("case 6!\n");
int tmp=2*(n-v[b].f+pmax[col^1][b-1]);
if(tmp>ans) ans=tmp,cnt=0;
if(tmp==ans) cnt+=1ll*pcmx[col^1][b-1]*v[b].cnt*(v[b].cnt-1)/2;
}
//(fa,sa)=(fb,sb)<(fc,sc)
//(0,0),(1,1)
if(scnt[col][b+1]){
//printf("case 7!\n");
int tmp=2*(smax[col][b+1]-v[b].f);
if(tmp>ans) ans=tmp,cnt=0;
if(tmp==ans) cnt+=1ll*scmx[col][b+1]*v[b].cnt*(v[b].cnt-1)/2;
}
//(1,0),(0,1)
if(scnt[col^1][b+1]){
//printf("case 8!\n");
int tmp=2*(n+v[b].f-smin[col^1][b+1]);
if(tmp>ans) ans=tmp,cnt=0;
if(tmp==ans) cnt+=1ll*scmn[col^1][b+1]*v[b].cnt*(v[b].cnt-1)/2;
}
//(fa,sa)=(fb,sb)=(fc,sc)
if("smr ak ioi"){
//printf("case 9!\n");
int tmp=0;
if(tmp==ans) cnt+=1ll*v[b].cnt*(v[b].cnt-1)*(v[b].cnt-2)/6;
}
}
printf("%lld",cnt);
return 0;
}
T4#
题面
程序员不能总是整天坐着编程。有时站起来离开办公桌,休息一下,与同事闲聊,甚至玩一会,也是十分好的主意。F 公司的程序员就特别喜欢一种球类游戏。
让我们想象一个在笛卡尔坐标系平面上玩的游戏。玩家坐落在点
平面上画有
计算玩家向任意方向扔一个球所能得到的最大分数。注意可能有实数坐标。
对于 30% 的数据,
对于 100% 的数据,
sol
很厉害的一道计算几何+差分。
对于一个圆看它能和多少个
相交/相切会产生交点。可以发现,在一条射线旋转的过程中(对应扔球方向),只有经过这些交点,才会影响到答案。
所以考虑对角度差分。若交点为
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long double LD;
const double pii=acos(-1);
const int M=2e4+10,N=5e5+10;
int cnt; LD pos[N],neg[N];
int n,d,ans,tot;
#define sq(x) ((x)*(x))
#define ichika(x,y) make_pair(x,y)
#define nino first
#define miku second
int main(){
scanf("%d%d",&n,&d);
for(int i=1,x,y,r;i<=n;i++){
scanf("%d%d%d",&x,&y,&r);
LD alpha=atan2(y,x);
if(alpha<0) alpha+=pii*2;
LD dis=sqrt(sq(x)+sq(y));
for(int k=ceil((dis-r)/d);k<=floor((dis+r)/d);k++){
LD R=k*d;
LD beta=acos((sq(dis)+sq(R)-sq(r))/(2*dis*R));
LD v1=alpha-beta,v2=alpha+beta;
if(v2>2*pii) v2-=2*pii,tot++;
pos[++cnt]=v1,neg[cnt]=v2;
}
}
ans=tot;
sort(pos+1,pos+1+cnt),sort(neg+1,neg+1+cnt);
for(int i=1,j=1;i<=cnt;){
LD ang=pos[i];
for(;i<=cnt&&pos[i]==ang;i++) tot++;
for(;j<=cnt&&neg[j]<ang;j++) tot--;
ans=max(ans,tot);
}
printf("%d",ans);
return 0;
}
2023.7.11#
T1#
题面
科罗拉多州的山脉是二维平面上的一条折线。这条折线由
罗恩打算为奶牛建造一个滑雪场,为此要在山脉上规划一条缆车线路。缆线也是一条折线,由若干段缆绳组成,起点在山脉的第一个端点,终点在最后一个端点。每段缆绳可以贴着山脉的轮廓,也可以悬浮于空中,跳过山脉上几个海拔低的端点。每段缆绳的水平跨度有限制,不能超过给定的整数
请帮助他规划一下,选择在山脉的哪些端点上修建,才能使得支柱数量最少?注意,根据题意,起点和终点上是一定要修建的。
sol
设
内存只有 64MB 所以开不了二维数组。
#include<cstdio>
#include<cstring>
const int M=5015;
const double inf=1e18;
template<typename T>
inline T max(T A,T B){
return A>B?A:B;
}
template<typename T>
inline T min(T A,T B){
return A<B?A:B;
}
//double mx[M][M];
int n,k,h[M];
double slope(int i,int j){
return (double)(h[i]-h[j])/(double)(i-j);
}
int f[M];
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&h[i]);
for(int i=1;i<=n;i++) f[i]=1e9+7; f[1]=1;
for(int i=1;i<n;i++){
double mx=-inf;
for(int j=i+1;j<=min(n,i+k);j++){
double s=slope(i,j);
if(s>=mx) f[j]=min(f[j],f[i]+1),mx=s;
}
}
printf("%d",(f[n]==1e9+7)?-1:f[n]);
return 0;
}
T2#
题面
[USACO06MAR] Milk Team Select G
Farmer John 的
每头奶牛都能为全队贡献一定量的牛奶,数值在
MMM 的目标是通过合作,增进家庭成员间的默契。为了支持比赛精神,奶牛们希望在赢得比赛的前提下,有尽可能多对奶牛间存在直系血缘关系。当然,所有奶牛都是女性,因此这里的直系血缘关系就是母女关系。
现在 FJ 摸清了所有奶牛间的血缘关系,希望算出一个团队在赢得胜利的前提下,最多有多少对奶牛存在血缘关系。注意:如果一个团队由某头奶牛和她的母亲和外祖母组成的话,这个团队只有两对血缘关系(她和她的母亲,她的母亲和外祖母)。
sol
加上
设
然后取最大的
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=1010,inf=1e9+7;
int n,X,a[M];
int f[M][M][2];
int head[M],cnte;
struct Edge{int to,next;}e[M];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
int siz[M];
void DP(int u){
f[u][0][0]=0,f[u][0][1]=a[u];
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to; DP(v);
for(int p=siz[u];p>=0;p--)
for(int q=siz[v];q>=0;q--){
f[u][p+q][0]=max(f[u][p+q][0],f[u][p][0]+max(f[v][q][0],f[v][q][1]));
f[u][p+q+1][1]=max(f[u][p+q+1][1],f[u][p][1]+f[v][q][1]);
f[u][p+q][1]=max(f[u][p+q][1],f[u][p][1]+f[v][q][0]);
}
siz[u]+=siz[v]+1;
}
}
int main(){
memset(head,-1,sizeof head);
memset(f,0xc0,sizeof f);
scanf("%d%d",&n,&X);
for(int i=1,f;i<=n;i++){
scanf("%d%d",&a[i],&f);
add(f,i);
}
DP(0); int ans=-1;
for(int i=0;i<=n;i++)
if(f[0][i][0]>=X) ans=i;
printf("%d",ans);
return 0;
}
T3#
题面
小 Z 离开家的时候忘记带走了钱包,掉下的硬币在桌子上排成了一列。正在等着哥哥回来的小 D 坐在桌子旁边,无聊地翻着桌子上的硬币。
出于某种爱好,小 D 一次一定会同时翻转 M 枚硬币。由于小 D 是一个爱动脑的小学生,这样进行了若干次之后她很快想到了一个问题:有多少种方法能够在 K 次翻转后把硬币由原来的状态变成现在这样呢?
因为小 D 是个好学的小学生,她只需要你告诉她方案数对 1000000007 取模的值以方便她进行验算就可以了。
对于 30% 的数据,
对于 60% 的数据,
对于 100% 的数据,
sol
dp。设
枚举第
选出
#include<cstdio>
#include<cstring>
#define int long long
const int M=221,mod=1e9+7;
int n,k,m,f[M][M];
char A[M],B[M]; int cnt;
int C[M][M];
void pre(){
for(int i=0;i<M;i++) C[i][i]=C[i][0]=1;
for(int i=1;i<M;i++)
for(int j=1;j<i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
signed main(){
pre();
scanf("%lld%lld%lld",&n,&k,&m);
scanf(" %s",A+1),scanf(" %s",B+1);
for(int i=1;i<=n;i++)
if(A[i]!=B[i]) cnt++;
f[0][cnt]=1;
for(int i=1;i<=k;i++)
for(int j=0;j<=n;j++)
for(int p=0;p<=m&&p<=j;p++){
int c=j-2*p+m;
if(c>=0&&c<=n)
f[i][c]=1ll*(f[i][c]+f[i-1][j]*C[j][p]%mod*C[n-j][m-p]%mod)%mod;
}
printf("%lld",f[k][0]);
return 0;
}
T4#
题面
小 Z 是一个爱好数学的小学生。最近,他在研究一些关于整数数列的性质。
为了方便他的研究,小Z希望实现一个叫做“Open Continuous Lines Processor”的数列编辑器。
一开始,数列编辑器里没有数字,只有一个光标。这个数列编辑器需要支持五种操作。
I x
在当前光标前插入数字 。D
删除当前光标前的数字。L
光标向前移动一个数字。R
光标向后移动一个数字。Q k
设光标之前的数列是 ,输出第 位及之前最大的前缀和,保证 。
对于
对于
对于
题目保证不会在数列编辑器为空时进行 D
操作。
sol
因为是前缀操作所以不必要 splay,用链表就行。
每次右移的时候维护节点信息即可。
#include<cstdio>
const int M=1e6+10,inf=1e9+7;
int n;
inline int max(int A,int B){return A>B?A:B; }
struct OCLP{
struct node{
int pre,nxt,mx,sum,val;
}l[M];
int head,tail,qwq,cur,id[M],ans[M];
void L(){cur=l[cur].pre;}
void R(){
cur=l[cur].nxt;
l[cur].sum=l[l[cur].pre].sum+l[cur].val;
l[cur].mx=max(l[l[cur].pre].mx,l[cur].sum);
ans[id[cur]=id[l[cur].pre]+1]=l[cur].mx;
}
void I(int x){
l[++qwq]=(node){head,tail,-inf,-inf,x};
l[qwq].pre=cur,l[qwq].nxt=l[cur].nxt;
l[l[qwq].nxt].pre=l[cur].nxt=qwq;
R();
}
void D(){
l[l[cur].pre].nxt=l[cur].nxt;
l[l[cur].nxt].pre=l[cur].pre;
L();
}
int Q(int k){return ans[k];}
OCLP(){
head=++qwq,tail=++qwq;
l[head]=(node){head,tail,-inf,0,0};
l[tail]=(node){head,tail,-inf,0,0};
cur=head;
}
}oclp;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
char opt; int x;
scanf(" %c",&opt);
if(opt=='I'){
scanf("%d",&x); oclp.I(x);
}else if(opt=='D'){
oclp.D();
}else if(opt=='L'){
oclp.L();
}else if(opt=='R'){
oclp.R();
}else if(opt=='Q'){
scanf("%d",&x);
printf("%d\n",oclp.Q(x));
}
}
return 0;
}
2023.7.12#
T1#
题面
在某个遥远的国家里,有
开车每经过一个城市,都会被收取一定的费用(包括起点和终点城市)。所有的收费站都在城市中,在城市间的公路上没有任何的收费站。
小红现在要开车从城市
在路上,每经过一个城市,她要交一定的费用。如果她某次交的费用比较多,她的心情就会变得很糟。所以她想知道,在她能到达目的地的前提下,她交的费用中最多的一次最少是多少。这个问题对于她来说太难了,于是她找到了聪明的你,你能帮帮她吗?
对于 60% 的数据,满足
对于 100% 的数据,满足
对于 100% 的数据,满足
sol
二分最小费用
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=1e5+10;
const long long inf=1e18;
int n,m,U,V,s,cost[M];
struct Graph{
int head[M],cnte;
int next[M],to[M],dis[M];
void add(int u,int v,int w){
to[++cnte]=v,next[cnte]=head[u];
dis[cnte]=w,head[u]=cnte;
}
Graph(){
memset(head,-1,sizeof head);
cnte=1;
}
}G;
long long dis[M]; bool vis[M];
typedef pair<long long,int> pli;
priority_queue<pli,vector<pli>,greater<pli>>q;
#define qwq make_pair
void Dijkstra(int x){
for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=false;
q.push(make_pair(dis[U]=0,U));
while(!q.empty()){
int u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=true;
for(int i=G.head[u];~i;i=G.next[i]){
int v=G.to[i];
if(dis[v]>dis[u]+G.dis[i]&&cost[v]<=x)
q.push(qwq(dis[v]=dis[u]+G.dis[i],v));
}
}
}
bool check(int x){
Dijkstra(x);
return dis[V]<=s;
}
int mxcost;
int main(){
scanf("%d%d%d%d%d",&n,&m,&U,&V,&s);
for(int i=1;i<=n;i++)
scanf("%d",&cost[i]),mxcost=max(mxcost,cost[i]);
for(int i=1,u,v,w;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
G.add(u,v,w),G.add(v,u,w);
}
int L=max(cost[U],cost[V]),R=mxcost,ans=-1;
while(L<=R){
int mid=(L+R)>>1;
if(check(mid)) ans=mid,R=mid-1;
else L=mid+1;
}
printf("%d",ans);
return 0;
}
T2#
题面
为了封印辉之环,古代塞姆利亚大陆的人民在异空间中建造了一座设备塔。
简单的说,这座设备塔是一个漂浮在异空间中的圆柱体,圆柱体两头的圆是计算核心,而侧面则是
传输信息所用的数据通道,划分成
然而,随着工作的继续进行,他们希望把侧面的一部分区块也改造成其他模块。然而,任何时候都
必须保证存在一条数据通道,能从圆柱体的一端通向另一端。
由于无法使用辉之环掌控下的计算系统,他们寻求你的帮助来解决这个问题。他们将逐个输入想要
改造的区域,而你则执行所有可行的改造并忽略可能导致数据中断的改造。
• 对于分值为 30 的子任务1,保证
• 对于分值为 30 的子任务2,保证
• 对于分值为 40 的子任务3,保证
sol
破环为链,把矩形复制一遍接到后面。
当加入一个点时,另一个矩形的对应点也要加入。
如果加入后存在八联通块和左右边界相接,那么这次操作不合法。
可以用并查集模拟。
#include<cstdio>
const int N=3010,M=2*3010*3010;
const int dx[]={-1,0,1,1,1,0,-1,-1},
dy[]={-1,-1,-1,0,1,1,1,0};
bool mark[M];
int n,m,k,cnt,ans;
int id(int x,int y){
return (x-1)*2*m+y;
}
int fa[M]; int dfn[M];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
fa[find(x)]=find(y);
}
void work(int x,int y){
int id0=id(x,y); mark[id0]=true;
for(int dir=0;dir<8;dir++){
int xx=x+dx[dir],yy=y+dy[dir];
if(xx<1||xx>n) continue;
if(yy<1) yy+=2*m; if(yy>2*m) yy-=2*m;
int id1=id(xx,yy);
if(mark[id1]) merge(id0,id1);
}
}
int main(){
scanf("%d%d%d",&n,&m,&k),cnt=2*n*m;
for(int i=1;i<=cnt;i++) fa[i]=i;
for(int i=1,x,y;i<=k;i++){
scanf("%d%d",&x,&y);
for(int dir=0;dir<8;dir++){
int xx=x+dx[dir],yy=y+dy[dir];
if(xx<1||xx>n) continue;
if(yy<1) yy+=2*m; if(yy>2*m) yy-=2*m;
int id0=id(xx,yy);
if(mark[id0]) dfn[find(id0)]=i;
}
bool flag=true;
for(int dir=0;dir<8;dir++){
int xx=x+dx[dir],yy=y+dy[dir]+m;
if(xx<1||xx>n) continue;
if(yy<1) yy+=2*m; if(yy>2*m) yy-=2*m;
int id0=id(xx,yy);
if(dfn[find(id0)]==i&&mark[id0]){flag=false;break;}
}
if(flag)
ans++,work(x,y),work(x,y+m);
}
printf("%d",ans);
return 0;
}
T3#
题面
小 K 和同学们期待已久的暑假即将到来。然而小 K 的班主任兼德育处主任却莫名失踪了一个月。据小道消息,他在高考后才会出现。但现在德育处有诸多繁杂的工作,确定每个班的“三好学生”便是最令人头痛的。
HY 中学的大 Boss 圆规要求
圆规从不在乎“三好学生”的数量,但是他却不得不考虑学生的感受。学生在学生会上
(1)提出了
(2)提出每个班至少一张奖状。
鉴于 HY 中学的高一高二学生在 5 月下旬某一个酷热的晚上,忍无可忍围起了整栋教学楼,并且齐声高声叫喊:“开空调!”但学校善后处理十分不得力。圆规决定全盘接受学生的建议,以平息学生的怒气。但是,他还是想知道最小的负能量。
20% 的数据
50% 的数据
70% 的数据
100% 的数据
sol
看着像差分约束,但是带了别的条件。其实是最小割。
首先对每个班建立一条链,每个学生是上面的一个点。连边
而每个班至少一张奖状,所以连边
对于
有可能出现负容量。设
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=2e5+10,inf=1e9+7;
int head[M],cnte=1;
struct Edge{int to,next,cap,flow;}e[M];
void add(int u,int v,int w){
e[++cnte]=(Edge){v,head[u],w,0};
head[u]=cnte;
}
void addedge(int u,int v,int w){
add(u,v,w),add(v,u,0);
}
int s,t,dis[M];
queue<int>q;
bool bfs(){
for(int i=s;i<=t;i++) dis[i]=0;
q.push(s),dis[s]=1;
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(!dis[v]&&e[i].cap>e[i].flow)
dis[v]=dis[u]+1,q.push(v);
}
}
return dis[t];
}
int dfs(int u,int in){
if(u==t) return in;
int out=0;
for(int i=head[u];~i&∈i=e[i].next){
int v=e[i].to;
if(e[i].cap>e[i].flow&&dis[v]==dis[u]+1){
int res=dfs(v,min(in,e[i].cap-e[i].flow));
e[i].flow+=res,e[i^1].flow-=res;
in-=res,out+=res;
}
}
if(!out) dis[u]=-114514;
return out;
}
int Dinic(){
int ans=0;
while(bfs()) ans+=dfs(s,inf);
return ans;
}
int n,m,k; int id(int x,int y){return (x-1)*m+y;}
int a[1010][110],minn=inf;
int main(){
freopen("honor.in","r",stdin);
freopen("honor.out","w",stdout);
memset(head,-1,sizeof head);
scanf("%d%d%d",&n,&m,&k);
s=0,t=n*m+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]),a[i][j]+=a[i][j-1];
minn=min(minn,a[i][j]);
}
for(int i=1;i<=n;i++) addedge(s,id(i,1),inf);
for(int i=1;i<=n;i++)
for(int j=1;j<m;j++)
addedge(id(i,j),id(i,j+1),a[i][j]-minn);
for(int i=1;i<=n;i++) addedge(id(i,m),t,a[i][m]-minn);
for(int i=1,u,v,w;i<=k;i++){
scanf("%d%d%d",&u,&v,&w);
for(int j=w+1;j<=m;j++) addedge(id(u,j),id(v,j-w),inf);
}
int cut=Dinic();
printf("%d",cut+minn*n);
return 0;
}
T4#
题面
外卖店一共有
比如 JYY 如果今天点了一份保质期为
JYY 现在有
送外卖的小哥身强力壮,可以瞬间给 JYY 带来任意多份食物。JYY 想知道,在满足每天都能吃到至少一顿没过期的外卖的情况下,他可以最多宅多少天呢?
对于全部的测试点,保证
sol
先将一些贵但是保质期短的食物去掉。
将所有食物按保质期排序,然后顺序插入栈中。如果栈顶的价格大于当前的价格(因为排序所以栈里的保质期短)就弹出栈顶。
然后假如你要买一次食物度过
然后如果点了
然后就是确定来的次数。事实上,宅的天数与点外卖次数成单峰函数关系。感性理解,如果小于某个次数,那么就要买更贵的食物;如果大于某个次数,那么买食物省下的钱都拿去付外卖费了。如果买
要开__int128
。
#include<cstdio>
#include<algorithm>
using namespace std;
const int M=210;
__int128 read(){
__int128 x=0; int f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
void print(__int128 x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9) print(x/10);
putchar(x%10+'0');
}
struct food{
__int128 p,s;
bool operator<(const food &o)const{return s<o.s;}
}F[M],stk[M]; int top;
int n;
__int128 m,f;
__int128 calc(__int128 T){
__int128 M=m-T*f,las=0,ans=0;
for(int i=1;i<=n;i++){
__int128 day=min(M/stk[i].p,(stk[i].s-las)*T);
if(day<=0) break;
M-=day*stk[i].p;
las+=day/T,ans+=day;
}
return ans;
}
int main(){
freopen("food.in","r",stdin);
freopen("food.out","w",stdout);
m=read(),f=read(); scanf("%d",&n);
for(int i=1;i<=n;i++)
F[i].p=read(),F[i].s=read()+1;
sort(F+1,F+1+n);
for(int i=1;i<=n;i++){
while(top>0&&stk[top].p>=F[i].p) top--;
stk[++top]=F[i];
}
n=top; for(int i=1;i<=n;i++) F[i]=stk[i];
__int128 L=1,R=m/f,ans=0;
while(L<=R){
__int128 m1=L+(R-L)/3,m2=R-(R-L)/3;
__int128 p1=calc(m1),p2=calc(m2);
if(p1<p2) L=m1+1,ans=max(ans,p1);
else R=m2-1,ans=max(ans,p2);
}
print(ans);
return 0;
}
2023.7.13#
T1#
题面
Samjia 和 Peter 不同,他喜欢玩树。所以 Peter送给他一颗大小为
Samjia 要给树上的每一个节点赋一个
多组数据。
测试点编号 | 特殊约定 | |
---|---|---|
1,2 | 无 | |
3,4 | 无 | |
5,6 | 第 |
|
7,8 | 第 |
|
9,10 | 无 |
对于所有数据,
sol
设
首先注意到括号内可以前缀和优化。
然后注意到
然后注意到
所以只需要求出
#include<cstdio>
#include<cstring>
#define int long long
const int M=110,mod=1e9+7;
inline int min(int A,int B){
return A<B?A:B;
}
int head[M],cnte;
struct Edge{int to,next;}e[M<<1];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
int T,n,m,k,mx;
int f[M][M*M],g[M][M*M];
int qsum(int u,int p){
if(p<=0) return 0;
if(p<=mx) return g[u][p];
if(p<=m-mx) return g[u][mx]+(f[u][mx]*(p-mx)%mod)%mod;
if(p>m-mx) return (g[u][mx]-g[u][m-p]+qsum(u,m-mx)+mod)%mod;
}
void DP(int u,int fa){
for(int j=1;j<=mx;j++) f[u][j]=1;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to; if(v==fa) continue;
DP(v,u);
for(int x=1;x<=mx+1;x++){
long long tmp=0;
if(x-k>0) tmp=(tmp+qsum(v,x-k))%mod;
if(x+k<=m) tmp=(tmp+qsum(v,m)-qsum(v,x+k-1)+mod)%mod;
f[u][x]=f[u][x]*tmp%mod;
}
}
for(int j=1;j<=mx;j++) g[u][j]=(g[u][j-1]+f[u][j])%mod;
}
signed main(){
freopen("label.in","r",stdin);
freopen("label.out","w",stdout);
scanf("%lld",&T);
while(T--){
memset(head,-1,sizeof head); cnte=1;
memset(f,0,sizeof f),memset(g,0,sizeof g);
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1,u,v;i<n;i++){
scanf("%lld%lld",&u,&v);
add(u,v),add(v,u);
}
if(k==0){
int ans=1;
for(int i=1;i<=n;i++) ans=1ll*ans*m%mod;
printf("%lld\n",ans);
continue;
}
mx=(m<=10000)?m:(n-1)*k+1;
DP(1,0);
printf("%lld\n",qsum(1,m));
}
return 0;
}
T2#
题面
Ducky 有一个大小为
他做了
sol
难以存下每个矩形中最大的正方形的边长。考虑二分答案,用一个
dp 求出
对于每次询问,二分矩形内是否存在边长为
所以需要一个快速求出矩形最大值的数据结构。树套树时间复杂度为
#pragma GCC optimize("Ofast")
#include<cmath>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int M=1010,N=1e6+10;
int lg2[M];
inline int read(){
int v=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') v=v*10+ch-'0',ch=getchar();
return v;
}
int n,m,t;
bool mp[M][M]; int mxs[M][M];
/*
int cntp;
struct node{
int lc,rc,val;
}tr[N<<4];
void pushup(int k){
tr[k].val=max(tr[tr[k].lc].val,tr[tr[k].rc].val);
}
void insert(int &k,int l,int r,int p,int v){
if(!k) k=++cntp;
if(l==r){
tr[k].val=max(tr[k].val,v);
return;
}
int mid=(l+r)>>1;
if(p<=mid) insert(tr[k].lc,l,mid,p,v);
else insert(tr[k].rc,mid+1,r,p,v);
pushup(k);
}
int query(int k,int L,int R,int l,int r){
if(!k) return 0;
if(L<=l && r<=R) return tr[k].val;
int mid=(l+r)>>1;
if(R<=mid) return query(tr[k].lc,L,R,l,mid);
else if(L>mid) return query(tr[k].rc,L,R,mid+1,r);
else return max(query(tr[k].lc,L,R,l,mid),query(tr[k].rc,L,R,mid+1,r));
}
struct node0{
int l,r,rt;
}tr0[M<<2];
void build(int k,int l,int r){
tr0[k].l=l,tr0[k].r=r;
if(l==r) return;
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
}
void ins(int k,int x,int y,int v){
insert(tr0[k].rt,1,m,y,v);
if(tr0[k].l==tr0[k].r) return;
int mid=(tr0[k].l+tr0[k].r)>>1;
if(x<=mid) ins(k<<1,x,y,v);
else ins(k<<1|1,x,y,v);
}
int qry(int k,int l0,int r0,int l1,int r1){
if(l0<=tr0[k].l&&tr0[k].r<=r0)
return query(tr0[k].rt,l1,r1,1,m);
int mid=(tr0[k].l+tr0[k].r)>>1;
if(r0<=mid) return qry(k<<1,l0,r0,l1,r1);
else if(l0>mid) return qry(k<<1|1,l0,r0,l1,r1);
else return max(qry(k<<1,l0,r0,l1,r1),qry(k<<1|1,l0,r0,l1,r1));
}*/
int st[11][11][M][M];
void build_st(){
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) st[0][0][i][j]=mxs[i][j];
for(int i=1;(1<<i)<=n;i++)
for(int k=1;k<=n-(1<<i)+1;k++)
for(int l=1;l<=m;l++)
st[i][0][k][l]=max(st[i-1][0][k][l],st[i-1][0][k+(1<<(i-1))][l]);
for(int j=1;(1<<j)<=m;j++)
for(int k=1;k<=n;k++)
for(int l=1;l<=m-(1<<j)+1;l++)
st[0][j][k][l]=max(st[0][j-1][k][l],st[0][j-1][k][l+(1<<(j-1))]);
for(int i=1;(1<<i)<=n;i++)
for(int j=1;(1<<j)<=m;j++)
for(int k=1;k<=n-(1<<i)+1;k++)
for(int l=1;l<=m-(1<<j)+1;l++){
st[i][j][k][l]=max(max(st[i-1][j-1][k][l],st[i-1][j-1][k+(1<<(i-1))][l]),
max(st[i-1][j-1][k][l+(1<<(j-1))],st[i-1][j-1][k+(1<<(i-1))][l+(1<<(j-1))]));
//printf("(%d,%d,%d,%d):\n (%d,%d,%d,%d)\n (%d,%d,%d,%d)\n (%d,%d,%d,%d)\n (%d,%d,%d,%d)\n",
// i,j,k,l,i-1,j-1,k,l,i-1,j-1,k+(1<<(i-1)),l,i-1,j-1,k,l+(1<<(j-1)),i-1,j-1,k+(1<<(i-1)),l+(1<<(j-1)));
}
// for(int i=0;(1<<i)<=n;i++)
// for(int j=0;(1<<j)<=m;j++)
// for(int k=1;k<=n-(1<<i)+1;k++)
// for(int l=1;l<=m-(1<<j)+1;l++)
// printf("st[%d][%d][%d][%d] = %d\n",i,j,k,l,st[i][j][k][l]);
}
int qry(int x0,int x1,int y0,int y1){
int k1=lg2[x1-x0+1],k2=lg2[y1-y0+1];
//int k1=log2(x1-x0+1),k2=log2(y1-y0+1);
int tmp=max(max(st[k1][k2][x0][y0],st[k1][k2][x1-(1<<k1)+1][y0]),
max(st[k1][k2][x0][y1-(1<<k2)+1],st[k1][k2][x1-(1<<k1)+1][y1-(1<<k2)+1]));
//printf("Qry(%d,%d,%d,%d) = %d, k1 = %d, k2 = %d\n",x0,y0,x1,y1,tmp,k1,k2);
return tmp;
}
int main(){
lg2[0]=-1;
for(int i=1;i<M;i++) lg2[i]=lg2[i>>1]+1;
freopen("square.in","r",stdin);
freopen("square.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) mp[i][j]=read();
for(int i=n;i>=1;i--)
for(int j=m;j>=1;j--)
if(mp[i][j])
mxs[i][j]=min(min(mxs[i+1][j],mxs[i][j+1]),mxs[i+1][j+1])+1;
/*
build(1,1,n);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(mp[i][j]) ins(1,i,j,mxs[i][j]);*/
build_st();
t=read();
for(int i=1,x0,y0,x1,y1;i<=t;i++){
x0=read(),y0=read(),x1=read(),y1=read();
/*
int L=1,R=min(min(x1-x0+1,y1-y0+1),qry(1,x0,x1,y0,y1)),ans=0;
while(L<=R){
int mid=(L+R)>>1;
if(qry(1,x0,x1-mid+1,y0,y1-mid+1)>=mid) ans=mid,L=mid+1;
else R=mid-1;
} */
int L=1,R=min(min(x1-x0+1,y1-y0+1),qry(x0,x1,y0,y1)),ans=0;
while(L<=R){
int mid=(L+R)>>1;
// printf("[%d,%d], mid = %d\n",L,R,mid);
if(qry(x0,x1-mid+1,y0,y1-mid+1)>=mid) ans=mid,L=mid+1;
else R=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
T3#
题面
现在给你一棵
对于树上的一个非空点集
现在请你求出,所有不同的点集的权值之和为多少,由于这个答案可能很大,你只需要输出它对
Note:两个点集不同,当且仅当点集中至少有一个元素不同。
sol
如果某条边的两个端点都被
考虑求出每个点
这样会算重复,因为
上述过程可以用点分治实现。可以把边
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define int long long
const int M=8e5+10,mod=998244353;
const int inf=1e9+7;
int max(int A,int B){return A>B?A:B;}
int n,ans,a[M],pw3[M];
void pre(){
pw3[0]=1;
for(int i=1;i<M;i++) pw3[i]=3ll*pw3[i-1]%mod;
}
int head[M],cnte;
struct Edge{int to,next;}e[M<<1];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
bool vis[M];
int rt,minsiz,totsiz,siz[M];
void getRoot(int u,int fa){
siz[u]=1; int tmp=0;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==fa||vis[v]) continue;
getRoot(v,u),siz[u]+=siz[v];
tmp=max(tmp,siz[v]);
}
tmp=max(tmp,totsiz-siz[u]);
if(tmp<minsiz) minsiz=tmp,rt=u;
}
int dep[M];
std::vector<int>ds,sub;
void getdep(int u,int fa){
sub.push_back(u),siz[u]=1;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==fa||vis[v]) continue;
dep[v]=dep[u]+1,getdep(v,u);
siz[u]+=siz[v];
}
}
int cnt[M];
#define qwq(a,x) lower_bound(a.begin(),a.end(),x)
void calc(int u,int w,bool del){
sub.clear(),ds.clear();
dep[u]=w,getdep(u,0);
for(int v:sub) ds.push_back(a[v]-dep[v]);
std::sort(ds.begin(),ds.end());
for(int v:sub){
int con=ds.end()-qwq(ds,dep[v]);
cnt[v]+=(del)?(-con):(con);
}
}
void solve(int u){
vis[u]=true,calc(u,0,false);
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(vis[v]) continue;
calc(v,1,true);
minsiz=inf,totsiz=siz[v];
getRoot(v,0),solve(rt);
}
}
signed main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
pre();
memset(head,-1,sizeof head);
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]*=2;
for(int i=1,u,v;i<n;i++){
scanf("%lld%lld",&u,&v);
add(u,i+n),add(i+n,u);
add(v,i+n),add(i+n,v);
}
minsiz=inf,totsiz=n;
getRoot(1,0),solve(rt);
for(int i=1;i<=n;i++) (ans+=pw3[cnt[i]]-1)%=mod;
for(int i=n+1;i<2*n;i++) (ans+=mod+1-pw3[cnt[i]-1])%=mod;
printf("%lld",ans);
return 0;
}
T4#
题面
在战火纷飞,饿殍遍野的黑暗时代,jambow 以其怀天下苍生的仁心和他人难以望其项背的实力,统一了 X501-G 大陆,建立起了塔克国。塔克国最高领袖的称号为“塔克”。
岁月更迭,由于地理位置以及其他种种复杂的原因,jambow 决定迁移首都。迁移首都有很多必要工作,而其中有一项便是重新规划地下水道。
jambow 由于这项工作繁杂且小弟们无一能胜任这项工作,所以决定亲自上阵,展现他作为塔克的实力。
虽然在之前 jambow 还没有研究过这类叫做网络流的问题,但以他的实力,
在学习过程中,jambow 抄了一些笔记:
通常在运筹学中,有向图称为网络,顶点称为节点而边称为弧。
如果带枚有限的有向图
- 有且仅有一个节点
入度为 ,称为源点。
2.有且仅有一个节点
3.
我们将通过容量网络中一条弧
在任意时刻,G的网络流均满足如下性质。
-
容量限制:
。 -
反对称性:
。 -
流守恒:
,有 。
对于一个容量网络,其最大流指的是在所有可能的网络流中,汇点收到的流量最大的网络流。
(以上为笔记内容)
jambow 思来想去,在
容量网络:有向图 → 无向图,任意两点都可为源点汇点。
弧:若
过了
jambow 很快就意识到这个问题对他的小弟来说有些困难,于是限制了
然而
对于所有数据,满足
对于前 10% 的数据,
对于前 30% 的数据,
对于另外 20% 的数据,
sol
实际上问你无序点对
可以直接上 Gomory-Hu Tree,但是因为度数
最大流等于最小割,所以考虑
-
当
不连通,显然最小割为 。 -
当
连通但是不在同一个边双连通分量,最小割为 。 -
当
在同一个边双联通分量,但是不在同一个边三连通分量,最小割为 。 -
当
在同一个点三连通分量,最小割为 。
参考这题的做法,可以把数据出到
如果暴力枚举
#include<cstdio>
#include<cstring>
#define gmin(a,b) (a=((a)<(b)?(a):(b)))
const int M=3010,N=4514;
int n,m,d,ans;
bool p1[M][M];
int head[M],cnte=1;
struct Edge{
int to,next; bool mark;
}e[N<<1];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u],false};
head[u]=cnte;
e[++cnte]=(Edge){u,head[v],false};
head[v]=cnte;
}
struct DSU{
int fa[M];
DSU(){for(int i=1;i<M;i++) fa[i]=i;}
int f(int x){return fa[x]==x?x:fa[x]=f(fa[x]);}
void m(int x,int y){fa[f(x)]=f(y);}
bool q(int x,int y){return f(x)==f(y);}
}dsu;
int stk[M],bel[M],dfn[M],low[M];
int idx,col,top,tmp;
void Tarjan(int u,int fa){
stk[++top]=u,low[u]=dfn[u]=++idx;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==fa||e[i].mark) continue;
if(!dfn[v]) Tarjan(v,u),gmin(low[u],low[v]);
else gmin(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
++col;
do bel[tmp=stk[top--]]=col; while(tmp!=u);
}
}
int bels[M][N]; // bels[i][j] 切掉第 j 条边后 i 所属的双连通分量编号
typedef unsigned long long ULL;
ULL B=1e8+7; ULL H[M];
int main(){
freopen("flow.in","r",stdin);
freopen("flow.out","w",stdout);
memset(head,-1,sizeof head);
scanf("%d%d%d",&n,&m,&d);
for(int i=1,x,y;i<=m;i++)
scanf("%d%d",&x,&y),add(x,y),dsu.m(x,y);
for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i,0);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(bel[i]!=bel[j]&&dsu.q(i,j)) ans++,p1[i][j]=true;
for(int E=1;E<=m;E++){
col=idx=0;
for(int i=1;i<=n;i++) bel[i]=dfn[i]=low[i]=0;
e[E<<1].mark=e[E<<1|1].mark=true;
for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i,0);
for(int i=1;i<=n;i++) bels[i][E]=bel[i];
e[E<<1].mark=e[E<<1|1].mark=false;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) H[i]=H[i]*B+bels[i][j];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(dsu.q(i,j)&&!p1[i][j]) ans+=2+(H[i]==H[j]);
printf("%d",ans);
return 0;
}
2023.7.14#
T1#
题面
今天是 Bessie 的生日,他买了一个蛋糕和朋友们一起分享,蛋糕可以看成是一个
40% 的数据,
60% 的数据,
100% 的数据,
sol
预处理出二维前缀和。
二分最少的那块能否达到
#pragma GCC optimize("Ofast")
#include<cstdio>
const int M=80;
int r,c,a[M][M];
int pre[M][M];
int qsum(int x0,int y0,int x1,int y1){
return pre[x1][y1]-pre[x0-1][y1]-pre[x1][y0-1]+pre[x0-1][y0-1];
}
bool check(int v){
// 最小值 >= v
for(int i=1;i<=r-3;i++)
for(int j=i+1;j<=r-2;j++)
for(int k=j+1;k<=r-1;k++){
int cnt=0,pre=0;
for(int l=1;l<=c;l++){
int v1=qsum(1,pre+1,i,l),v2=qsum(i+1,pre+1,j,l);
int v3=qsum(j+1,pre+1,k,l),v4=qsum(k+1,pre+1,r,l);
if(v1>=v&&v2>=v&&v3>=v&&v4>=v){
pre=l,cnt++;
if(cnt>=4) return true;
}
}
}
return false;
}
int main(){
scanf("%d%d",&r,&c);
for(int i=1;i<=r;i++)
for(int j=1;j<=c;j++) scanf("%1d",&a[i][j]);
for(int i=1;i<=r;i++)
for(int j=1;j<=c;j++)
pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+a[i][j];
int ans=0;
int L=0,R=pre[r][c];
while(L<=R){
int mid=(L+R)>>1;
if(check(mid)) L=mid+1,ans=mid;
else R=mid-1;
}
printf("%d",ans);
return 0;
}
T2#
题面
新入生欢迎庆典马上要开始了,唯的吉太的弦却出了一些问题,在前一段时间换弦的时候弄乱了,但是唯根本不会修,所以只能拜托澪。
具体来说是这样的,一共有
一共会取出
Subtask 1(10 pts),
Subtask 2(10 pts),
Subtask 3(20 pts),
Subtast 4(20 pts),
Subtask 5(20 pts),
Subtask 6(20 pts),
sol
通过单调栈求出每个右端点
具体来说,将点从左到右插入单调栈。当插入左端点时,将左端点以前的区间全部弹出;当插入右端点时,将其对应区间的内部包含的区间弹出,并将交叉的区间合并即可。
最后会形成一颗树(链?),询问实际上就是求
树上倍增会 T 掉,而且这题的树分叉较少,所以用树剖。
#include<cstdio>
#include<cstring>
#include<iostream>
const int M=2e6+10;
inline int max(int A,int B){
return A>B?A:B;
}
inline int min(int A,int B){
return A<B?A:B;
}
inline int read(){
int x=0; char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
int p[M],n,m,rt;
int stk[M],l[M],r[M],top;
int head[M],cnte;
struct Edge{int to,next;}e[M];
void add(int u,int v){
// printf("add(%d,%d)\n",u,v);
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
int fa[M],son[M],sz[M],d[M],tp[M];
void dfs1(int u,int f){
fa[u]=f,d[u]=d[f]+1,sz[u]=1;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
dfs1(v,u),sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
tp[u]=t; if(!son[u]) return;
dfs2(son[u],t);
for(int i=head[u];~i;i=e[i].next)
if(e[i].to!=son[u]) dfs2(e[i].to,e[i].to);
}
int LCA(int u,int v){
while(tp[u]!=tp[v]){
// std::cerr<<"u="<<u<<" v="<<v<<'\n';
if(d[tp[u]]>d[tp[v]]) u=fa[tp[u]];
else v=fa[tp[v]];
}
return d[u]<d[v]?u:v;
}
int main(){
freopen("hotchkiss.in","r",stdin);
freopen("hotchkiss.out","w",stdout);
memset(head,-1,sizeof head);
n=read(),m=read(),rt=2*n+1;
for(int i=1;i<=2*n;i++) p[i]=read();
for(int i=1;i<=2*n;i++){
l[i]=min(p[i],i),r[i]=max(p[i],i);
while(stk>0 && l[i]<=stk[top]){
int x=stk[top--];
l[i]=min(l[i],l[x]),r[i]=max(r[i],r[x]);
}
stk[++top]=i;
}
memset(fa,-1,sizeof fa);
for(int i=1;i<=2*n;i++)
if(r[i]==i) add(l[i]-1,i),fa[i]=l[i]-1;
for(int i=0;i<=2*n;i++)
if(fa[i]==-1) add(rt,i),fa[i]=rt;
dfs1(rt,rt+1),dfs2(rt,rt);
for(int i=1,r1,r2;i<=m;i++){
scanf("%d%d",&r1,&r2);
int lca=LCA(r1,r2);
if(lca==rt) printf("0\n");
else printf("%d\n",d[lca]-2);
}
return 0;
}
T3#
题面
有个房子摆成一列,从左到右分别标号为
现在一个人想走路,他有
他还希望他能刚好使用所有颜色的旗子。他想知道他插旗子的方案数。
1 l r k
表示将
2 l r k
表示人想从
对 100% 数据,有
sol
注意不是
假设现在有一个高度单调不降,长度为
设
根据二项式反演,
中括号内的柿子,可以考虑对每一个底数
合并两个节点的时候,转移柿子是
这样是正确的,因为左右底数相同,指数表示旗子个数。若左半区间存在
还有区间摊平操作。当整个区间高度都为
#include<cstdio>
#include<cstring>
#define int long long
#define memcyo(x,y) memset(x,y,sizeof x)
const int M=5e4+10,mod=1e9+7;
int n,q,h[M];
int pw[7][M],C[7][7];
void pre(){
C[0][0]=1;
for(int i=1;i<=6;i++){
pw[i][0]=1;
for(int j=1;j<M;j++)
pw[i][j]=1ll*pw[i][j-1]*i%mod;
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
}
struct node{
int l,r,lazy;
int f[4][4][6];
}tr[M<<2];
#define lc(k) (k<<1)
#define rc(k) (k<<1|1)
void tag(int k,int v){
tr[k].lazy=v;
memcyo(tr[k].f,0);
for(int T=0;T<=5;T++)
tr[k].f[v][v][T]=pw[T+1][tr[k].r-tr[k].l+1]-1;
}
void pushdown(int k){
if(!tr[k].lazy) return;
tag(lc(k),tr[k].lazy),tag(rc(k),tr[k].lazy);
tr[k].lazy=0;
}
node merge(node A,node B){
node C;
C.l=A.l,C.r=B.r,C.lazy=0;
memcyo(C.f,0);
for(int T=0;T<=5;T++)
for(int h0=1;h0<=3;h0++)
for(int h3=h0;h3<=3;h3++){
C.f[h0][h3][T]=(A.f[h0][h3][T]+B.f[h0][h3][T])%mod;
for(int h1=h0;h1<=h3;h1++)
for(int h2=h1;h2<=h3;h2++)
(C.f[h0][h3][T]+=1ll*A.f[h0][h1][T]*B.f[h2][h3][T]%mod)%=mod;
}
return C;
}
void build(int k,int l,int r){
tr[k].l=l,tr[k].r=r;
if(l==r){
memcyo(tr[k].f,0);
for(int T=0;T<=5;T++)
tr[k].f[h[l]][h[l]][T]=T;
return;
}
int mid=(l+r)>>1;
build(lc(k),l,mid),build(rc(k),mid+1,r);
tr[k]=merge(tr[lc(k)],tr[rc(k)]);
}
void update(int k,int l,int r,int v){
if(l<=tr[k].l&&tr[k].r<=r){
tag(k,v); return;
}
pushdown(k);
int mid=(tr[k].l+tr[k].r)>>1;
if(l<=mid) update(lc(k),l,r,v);
if(r>mid) update(rc(k),l,r,v);
tr[k]=merge(tr[lc(k)],tr[rc(k)]);
}
node query(int k,int l,int r){
if(l<=tr[k].l&&tr[k].r<=r) return tr[k];
pushdown(k);
int mid=(tr[k].l+tr[k].r)>>1;
if(r<=mid) return query(lc(k),l,r);
else if(l>mid) return query(rc(k),l,r);
else return merge(query(lc(k),l,r),query(rc(k),l,r));
}
signed main(){
freopen("sendpoints.in","r",stdin);
freopen("sendpoints.out","w",stdout);
pre();
scanf("%lld%lld",&n,&q);
for(int i=1;i<=n;i++) scanf("%lld",&h[i]);
build(1,1,n);
for(int i=1,opt,l,r,k;i<=q;i++){
scanf("%lld%lld%lld%lld",&opt,&l,&r,&k);
if(opt==1) update(1,l,r,k);
if(opt==2){
node res=query(1,l,r);
int ans=0;
for(int T=0;T<=k;T++){
int sum=0;
for(int h0=1;h0<=3;h0++)
for(int h1=h0;h1<=3;h1++)
(sum+=res.f[h0][h1][k-T])%=mod;
int qwq=(T&1)?(mod-1):1;
ans=(ans+1ll*qwq*C[k][T]%mod*sum%mod)%mod;
}
printf("%lld\n",ans);
}
}
return 0;
}
T4#
题面
沈先生是 X 国 503 城有名的大资本家。
X 国有
沈先生现在在城市
走过一条路需要时间,但是这个时间现在还不知道,只知道时间
虽然沈先生并不能确定是哪条道路用时最少,但还是钦定了一条路径。
小 C 认为这条路径钦定的很差,于是就打算告诉沈先生这个路径最短的前缀,使得走这段路一定用时不是最短的。
小 C
Subtask 1 (20 pts)
保证
Subtask 2 (20 pts)
Subtask 3 (20 pts)
Subtask 4 (20 pts)
Subtask 5 (20 pts)
没有特殊限制。
对于全部数据,
sol
二分走前
「一定不是最短的」意味着存在一条长度不超过它的路径。可以考虑一次从
但是会出现二者走同一条边的情况,根据贪心这条边要么取
#include<queue>
#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
const int M=2e5+10,inf=1e18;
int n,m,p,l[M],r[M],ed[M];
int head[M],cnte;
struct Edge{int to,next;}e[M];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
int dis[2][M],mark[M];
bool vis[2][M];
typedef pair<int,int> miku;
priority_queue<miku,vector<miku>,greater<miku>>q;
#define qwq make_pair
bool Dijkstra(int mid){
// mark[i] = 0:未经过的边
// mark[i] = 1: 经过了,设置边权为 r[i]
// mark[i] = 2: 经过了,设置边权为 l[i]
for(int i=1;i<=n;i++)
for(int j=0;j<2;j++) dis[j][i]=inf,vis[j][i]=false;
for(int i=1;i<=m;i++) mark[i]=0;
for(int i=1;i<=mid;i++) mark[ed[i]]=2;
dis[0][1]=0; int pre=0;
for(int i=1;i<=mid;i++){
dis[0][e[ed[i]].to]=(pre+=l[ed[i]]*2);
if(i!=mid&&e[ed[i]].to==2) return true;
}
dis[1][1]=1;
q.push(qwq(dis[0][e[ed[mid]].to],e[ed[mid]].to));
q.push(qwq(dis[1][1],1));
while(!q.empty()){
miku u=q.top(); q.pop();
int cur=u.first&1;
if(vis[cur][u.second]) continue;
vis[cur][u.second]=true;
for(int i=head[u.second];~i;i=e[i].next){
int v=e[i].to,w;
if(mark[i]) w=(mark[i]==2)?(2*l[i]):(2*r[i]);
else mark[i]=(cur==1)?1:2,w=(cur==1)?(2*r[i]):(2*l[i]);
if(dis[cur][v]>dis[cur][u.second]+w)
q.push(qwq(dis[cur][v]=dis[cur][u.second]+w,v));
}
}
return dis[0][2]>=dis[1][2];
}
signed main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
memset(head,-1,sizeof head);
scanf("%lld%lld%lld",&n,&m,&p);
for(int i=1,u,v;i<=m;i++){
scanf("%lld%lld%lld%lld",&u,&v,&l[i],&r[i]);
add(u,v);
}
for(int i=1;i<=p;i++) scanf("%lld",&ed[i]);
int ans=0;
for(int L=1,R=p;L<=R;){
int mid=(L+R)>>1;
if(Dijkstra(mid)) ans=mid,R=mid-1;
else L=mid+1;
}
if(ans==0) printf("No Response!");
else printf("%lld",ed[ans]);
return 0;
}
2023.7.15#
T1#
题面
JY 是一个爱旅游的探险家,也是一名强迫症患者。现在 JY 想要在 C 国进行一次长途旅行,C 国拥有
若可以恰好到达输出Possible
否则输出Impossible
。
多组数据。
对于 30% 的数据,
另有 30% 的数据,
对于全部数据,
sol
考虑第一档分,设
现在想办法缩小 T 的范围。具体的可以选择一条边
这样子每条路径的长度都是
最后如果
#include<queue>
#include<cstdio>
#include<cstring>
#include<utility>
#define int long long
const int M=51,N=20010;
const int inf=1e18+7;
typedef std::pair<int,int> pii;
#define qwq std::make_pair
#define nino first
#define miku second
inline int min(int A,int B){
return A<B?A:B;
}
int Case,n,m,k;
long long T;
int head[M],cnte;
struct Edge{int to,next,dis;}e[N];
void add(int u,int v,int w){
e[++cnte]=(Edge){v,head[u],w};
head[u]=cnte;
}
std::queue<pii>q;
int dis[M][N]; bool inq[M][N];
// dis[i][j]:从 0 走到 i,路程余数为 j 的最短路
void SPFA(){
for(int i=0;i<M;i++)
for(int j=0;j<N;j++) dis[i][j]=inf;
dis[0][0]=0; q.push(qwq(0,0)),inq[0][0]=true;
while(!q.empty()){
pii u=q.front(); q.pop();
inq[u.nino][u.miku]=false;
for(int i=head[u.nino];~i;i=e[i].next){
int v=e[i].to,disv=dis[u.nino][u.miku]+e[i].dis;
if(dis[v][disv%k]>disv){
dis[v][disv%k]=disv;
if(!inq[v][disv%k])
inq[v][disv%k]=true,q.push(qwq(v,disv%k));
}
}
}
}
signed main(){
scanf("%lld",&Case);
while(Case--){
memset(head,-1,sizeof head),cnte=1;
scanf("%lld%lld%lld",&n,&m,&T);
k=inf;
for(int i=1,u,v,w;i<=m;i++){
scanf("%lld%lld%lld",&u,&v,&w);
add(u,v,w),add(v,u,w);
if(u==0||v==0) k=min(k,w*2);
}
if(k==inf){
printf("Impossible\n");
continue;
}else{
SPFA();
if(dis[n-1][T%k]>T) printf("Impossible\n");
else printf("Possible\n");
}
}
return 0;
}
T2#
题面
小 P 最喜欢的数据结构是线段树。线段树是一棵完全二叉树,其每个节点都对应着一个区间
小 P 的好朋友,小 X,对于一般图最大匹配问题的确定性算法很有研究。匹配是指原图的一个边集,满足集合内的任意两条边都没有相同的端点。最大匹配是所有匹配中所选边集大小最大的匹配方案。
一天,小 X 拿出了一棵表示
sol
树形 DP。设
事实上,同一层最多有 2 种不同的区间长度,而同种区间长度所建成的线段树同构。所以区间长度最多
对于每个
用map
复杂度会变成 unordered_map
,但是仍然会 T。预处理出区间长度
#include<map>
#include<cstdio>
#include<utility>
#include<unordered_map>
#define qwq make_pair
#define nino first
#define miku second
#define int long long
typedef std::pair<int,int> pii;
const int mod=998244353;
const int M=1e6+10;
inline int max(int A,int B){
return A>B?A:B;
}
pii df[M],dg[M];
int T,n;
void process(pii fl,pii fr,pii gl,pii gr,pii &fu,pii &gu){
fu.nino=max(fl.nino,fl.miku)+max(fr.nino,fr.miku);
fu.miku=1+max(fl.nino+max(fr.nino,fr.miku),fr.nino+max(fl.nino,fl.miku));
gu=std::qwq(0,0);
int tl=0,tr=0;
if(fl.nino>fl.miku) tl=gl.nino;
else if(fl.nino<fl.miku) tl=gl.miku;
else tl=gl.nino+gl.miku;
if(fr.nino>fr.miku) tr=gr.nino;
else if(fr.nino<fr.miku) tr=gr.miku;
else tr=gr.nino+gr.miku;
gu.nino=tl*tr%mod;
int p1=1+fl.nino+max(fr.nino,fr.miku);
int p2=1+fr.nino+max(fl.nino,fl.miku);
if(fu.miku==p1)
gu.miku=(gu.miku+gl.nino*tr%mod)%mod;
if(fu.miku==p2)
gu.miku=(gu.miku+gr.nino*tl%mod)%mod;
}
std::unordered_map<int,pii>f,g;
pii dfs(int u){
if(u<=1000000){g[u]=dg[u];return df[u];}
if(f.count(u)) return f[u];
int l=u/2,r=u-u/2;
pii fl=dfs(l),fr=dfs(r),ff,gg;
process(fl,fr,g[l],g[r],ff,gg);
g[u]=gg,f[u]=ff;
return ff;
}
signed main(){
freopen("match.in","r",stdin);
freopen("match.out","w",stdout);
dg[1]=std::qwq(1,0);
for(int u=2;u<=1000000;u++){
int l=u/2,r=u-u/2;
process(df[l],df[r],dg[l],dg[r],df[u],dg[u]);
}
scanf("%lld",&T);
while(T--){
scanf("%lld",&n);
pii p=dfs(n);
int ans1=0,ans2=0;
if(p.nino>p.miku) ans1=p.nino,ans2=g[n].nino;
else if(p.nino<p.miku) ans1=p.miku,ans2=g[n].miku;
else ans1=p.nino,ans2=(g[n].nino+g[n].miku)%mod;
printf("%lld %lld\n",ans1,ans2);
f.clear(),g.clear();
}
return 0;
}
T3#
题面
但是小 P 是一个肥宅,他并不喜欢走路。于是他找小 X 借来了旅行模拟器,开始了他的云上之旅。
小 P 的旅游地图是一棵
而同时,旅行模拟器为了吸引更多的用户,会进行若干次更新,每次更新后所有旅游景点都会发生翻天覆地的变化。因此,小 P 决定进行
sol
一个显然的贪心策略,就是每次走贡献最多的路径。
操作次数达到
事实上,每次询问的就是前
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gmax(A,B) (A=std::max(A,B))
typedef long long miku;
const int N=5e6+10,M=2e6+10;
const miku mod=2148473648;
namespace io {
const int SIZE = 1 << 22 | 1;
char iBuf[SIZE], *iS, *iT, c;
char oBuf[SIZE], *oS = oBuf, *oT = oBuf + SIZE;
#define gc() (iS == iT ? iT = iBuf + fread(iS = iBuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS++) : *iS++)
template<class I> void gi(I &x) {
int f = 1;
for(c = gc(); c < '0' || c > '9'; c = gc())
if(c == '-') f = -1;
for(x = 0; c >= '0' && c <= '9'; c = gc())
x = (x << 3) + (x << 1) + (c & 15);
x *= f;
}
inline void flush() {
fwrite(oBuf, 1, oS - oBuf, stdout);
oS = oBuf;
}
inline void putc(char x) {
*oS++ = x;
if(oS == oT) flush();
}
template<class I> void print(I x) {
if(x < 0) putc('-'), x = -x;
static char qu[55];
char *tmp = qu;
do *tmp++ = (x % 10) ^ '0';
while(x /= 10);
while(tmp-- != qu) putc(*tmp);
}
struct flusher {
~flusher() {
flush();
}
} _;
}
using io :: gi;
using io :: putc;
using io :: print;
miku pw233[M];
void pre(){
pw233[0]=1;
for(int i=1;i<M;i++) pw233[i]=pw233[i-1]*233%mod;
}
int n,m,l;
int head[N],cnte;
struct Edge{int to,next;}e[N];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
struct node{int dep,mxdep,h,id,fa,son,top;}p[N];
struct qry{int d,id;}q[M];
void dfs(){
for(int u=1;u<=n;u++){
if(u!=1) p[u].dep=p[p[u].fa].dep+1;
p[u].mxdep=p[u].dep;
}
for(int u=n;u>=1;u--)
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;;
gmax(p[u].mxdep,p[v].mxdep);
if(p[v].mxdep>p[p[u].son].mxdep) p[u].son=v;
}
for(int u=1;u<=n;u++){
if(u==1) p[u].top=1;
else if(u==p[p[u].fa].son) p[u].top=p[p[u].fa].top;
else p[u].top=u;
}
for(int u=n;u>=1;u--){
if(!p[u].son) p[u].h=1;
else p[u].h=p[p[u].son].h+1;
}
}
bool mark[N];
miku tot,cnt,ans,limh;
int buc[N],rev[N];
signed main(){
freopen("tour.in","r",stdin);
freopen("tour.out","w",stdout);
pre();
memset(head,-1,sizeof head);
gi(n),gi(m),gi(l);
for(int i=1;i<=n;i++) p[i].id=i;
for(int i=2;i<=n;i++) gi(p[i].fa),add(p[i].fa,i);
dfs();
for(int i=1;i<=n;i++){
if(mark[p[i].top]) continue;
mark[p[i].top]=true,buc[p[p[i].top].h]++;
}
for(int i=n;i>=1&&cnt<l;i--){
if(!buc[i]) continue;
limh=i;
if(cnt+buc[i]<=l) (tot+=buc[i]*i%mod)%=mod;
else (tot+=(l-cnt)*i%mod)%=mod;
cnt+=buc[i];
}
std::sort(p+1,p+1+n,[](node A,node B){return A.dep>B.dep;});
for(int i=1;i<=n;i++) rev[p[i].id]=i;
for(int i=1;i<=m;i++) gi(q[i].d),q[i].id=i;
std::sort(q+1,q+1+m,[](qry A,qry B){return A.d>B.d;});
int cur=1;
for(int i=1;i<=m;i++){
while(cur<=n&&p[cur].dep>q[i].d){
int tp=rev[p[cur].top];
if(p[tp].h>limh) tot--;
else if(p[tp].h==limh){
if((--cnt)<l) tot--,cnt+=buc[--limh]+1;
}
buc[p[tp].h]--,buc[--p[tp].h]++;
cur++;
}
(ans+=tot*pw233[q[i].id]%mod)%=mod;
}
printf("%lld",ans);
return 0;
}
T4#
咕咕咕
GDKOI 2023 D1T3
2023.7.16#
没做
2023.7.19#
T1#
题面
Venezuela 是一个动荡不安的国家。Venezuela 可以视为一个
多组数据。
保证数据合法,
sol
视
先 bfs 跑最短路,求出
接着对于每个点,设只有
#include<queue>
#include<cstdio>
#include<cstring>
const int M=1010,mod=998244353;
int T,n,m,k,inv[M];
int head[M],cnte;
struct Edge{int to,next;}e[M<<1];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
bool vis[M];
int dis[2][M],p[M][M],f[M];
// p[i][j]: 势力 j 不经过 i 的概率
std::queue<int>q;
void bfs(int s,int t,int c){
for(int i=1;i<=n;i++)
for(int j=0;j<2;j++) dis[j][i]=-1;
for(int i=1;i<=n;i++) f[i]=0,vis[i]=false;
q.push(s),dis[0][s]=0;
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=head[u];~i;i=e[i].next)
if(dis[0][e[i].to]==-1)
dis[0][e[i].to]=dis[0][u]+1,q.push(e[i].to);
}
q.push(t),dis[1][t]=0;
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=head[u];~i;i=e[i].next)
if(dis[1][e[i].to]==-1)
dis[1][e[i].to]=dis[1][u]+1,q.push(e[i].to);
}
q.push(s),f[s]=1;
while(!q.empty()){
int u=q.front(); q.pop();
if(vis[u]) continue; vis[u]=true;
p[u][c]=1ll*p[u][c]*(mod+1-f[u])%mod;
int cnt=0;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(dis[0][t]==dis[0][u]+1+dis[1][v]) cnt++;
}
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(dis[0][t]==dis[0][u]+1+dis[1][v]){
f[v]=(f[v]+1ll*f[u]*inv[cnt]%mod)%mod;
q.push(v);
}
}
}
}
int pre[M];
int main(){
freopen("sukeban.in","r",stdin);
freopen("sukeban.out","w",stdout);
inv[1]=1;
for(int i=2;i<M;i++)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
memset(head,-1,sizeof head);
scanf("%d",&T);
while(T--){
for(int i=0;i<M;i++)
for(int j=0;j<M;j++) p[i][j]=1;
memset(head,-1,sizeof head),cnte=1;
scanf("%d%d%d",&n,&m,&k);
for(int i=1,u,v;i<=m;i++)
scanf("%d%d",&u,&v),add(u,v),add(v,u);
for(int i=1,c,a,b;i<=k;i++)
scanf("%d%d%d",&c,&a,&b),bfs(a,b,c);
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
p[i][j]=(mod+1-p[i][j])%mod;
for(int i=1;i<=n;i++){
int ans=1; pre[0]=1;
for(int j=1;j<=k;j++){
pre[j]=1ll*pre[j-1]*(mod+1-p[i][j])%mod;
ans=1ll*ans*(mod+1-p[i][j])%mod;
}
ans=(1+mod-ans)%mod; int suf=1;
for(int j=k;j>=1;j--){
ans=(ans+mod-1ll*pre[j-1]*p[i][j]%mod*suf%mod)%mod;
suf=1ll*suf*(1+mod-p[i][j])%mod;
}
printf("%d\n",ans);
}
}
return 0;
}
T2#
题面
某国有
不过,小 X 只对其中
为了更好地规划模拟旅行路线,提升模拟旅行的体验,小X想要知道他感兴趣的城市之间两两最短路的最小值(即在他感兴趣的城市中,最近的一对的最短距离)。
作为一个肥宅,小 X 根本懒得写程序来解决这道问题,于是他把这个问题丢给了你。
sol
省选原题的加强版,卡掉了 2 只
建出原图和反图,分别求出对于每个节点「到哪个关键点最近」和「从哪个关键点来最近」。设前者为
#include<queue>
#include<cstdio>
#include<cstring>
const int M=3e5+10,N=1e6+10;
typedef long long MIKU;
const MIKU inf=1e18;
int n,m,k,p[M];
struct Graph{
int head[M],cnte;
int to[N],next[N],dis[N];
void add(int u,int v,int w){
to[++cnte]=v,next[cnte]=head[u];
dis[cnte]=w,head[u]=cnte;
}
Graph(){
memset(head,-1,sizeof head);
cnte=1;
}
}G[2];
struct Edge{int u,v,w;}e[N];
typedef std::pair<MIKU,int> pmiku;
std::priority_queue<pmiku,std::vector<pmiku>,std::greater<pmiku>>q;
MIKU dis[2][M]; bool vis[2][M]; int col[2][M];
#define qwq std::make_pair
void Dijkstra(int T){
for(int i=1;i<=n;i++)
dis[T][i]=inf,vis[T][i]=false,col[T][i]=0;
for(int i=1;i<=k;i++){
q.push(qwq(dis[T][p[i]]=0,p[i]));
col[T][p[i]]=i;
}
while(!q.empty()){
int u=q.top().second; q.pop();
if(vis[T][u]) continue; vis[T][u]=true;
for(int i=G[T].head[u];~i;i=G[T].next[i]){
int v=G[T].to[i];
if(dis[T][v]>dis[T][u]+G[T].dis[i]){
dis[T][v]=dis[T][u]+G[T].dis[i];
q.push(qwq(dis[T][v],v));
col[T][v]=col[T][u];
}
}
}
}
int main(){
freopen("tour.in","r",stdin);
freopen("tour.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
if(e[i].u==e[i].v) continue;
G[0].add(e[i].u,e[i].v,e[i].w);
G[1].add(e[i].v,e[i].u,e[i].w);
}
for(int i=1;i<=k;i++) scanf("%d",&p[i]);
Dijkstra(0),Dijkstra(1);
MIKU ans=inf;
for(int i=1;i<=m;i++)
if(col[0][e[i].u]&&col[1][e[i].v])
if(col[0][e[i].u]!=col[1][e[i].v])
ans=std::min(ans,dis[0][e[i].u]+e[i].w+dis[1][e[i].v]);
printf("%lld",ans);
return 0;
}
T3#
题面
10s, 512MB.
众所周知,在一场考试中,三道题是相对独立的,这是因为题目考察的知识点往往不同,不同的知识点往往又具有一定独立性。因此解决一道题目与掌握这道题目涉及的算法知识密切相关。
小 L 想知道他最应该学习什么,小 L 的请求用我们所学的 OI 模型来描述大概是这样的:
-
将比赛看作一棵
个点的树形结构,每个点代表一场比赛且具有三个数
,这对应这场比赛中三道题涉及的知识点; -
小 L 的竞赛历程可以视作树上的一条路径
,你需要求出小 L 最应该学习什么知识,即这条路径上出现次数最多的数是什么(一个点上如果有相同的数算作出现多次); -
由于世界线的变动,小 L 的竞赛历程可能有微小的变化,因此有多次询问,这里用
表示询问次数。
请你完成小 L 的请求。
对于 50% 的数据,不强制在线,数据较为随机且具有一定梯度。
对于另外 50% 的数据,强制在线。
对于 100% 的数据,
sol
区间众数上树,考虑树分块。
在树上取
然后把所有关键点两两的 LCA 以及节点
查询的时候,分几种情况:
路径上没有关键点/只有一个关键点。
两点之间距离期望为
路径上有关键点, 上没有。
找到
路径上没有关键点, 上有。
同 2.
- 两段路径都有关键点。
找到离
找到最近的关键点是预处理的,最远的关键点可以树上倍增找,搭配上预处理的祖先中「关键点的个数」即可。
可以在线化莫队。
#pragma GCC optimize("Ofast")
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
const int M=8e4+10,S=300;
const int N=M/S*2;
int Type,n,q,xorsum,a[M][3];
int head[M],cnte;
struct Edge{int to,next;}e[M<<1];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
int sz0[M];
std::vector<int>key;
int fa[M],son[M],sz[M],dep[M];
int anc[M][18],dfn[M],idx;
void dfs1(int u,int f){
dfn[u]=++idx;
fa[u]=f,dep[u]=dep[f]+1;
sz[u]=sz0[u]=1;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==f) continue;
dfs1(v,u);
sz[u]+=sz[v],sz0[u]+=sz0[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
if(sz0[u]>=S) key.push_back(u),sz0[u]=0;
}
int top[M];
void dfs2(int u,int t){
top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
int LCA(int u,int v){
while(top[u]!=top[v])
if(dep[top[u]]>dep[top[v]]) u=fa[top[u]];
else v=fa[top[v]];
return dep[u]<dep[v]?u:v;
}
bool isKey[M];
int keyid[M];
int bc[M],keyfa[M],keycnt[M];
int cnt[N][M];
void dfs3(int u,int lastkey){
for(int j=0;j<3;j++) bc[a[u][j]]++;
if(isKey[u]){
lastkey=u;
int ID=keyid[u];
memcpy(cnt[ID],bc,sizeof bc);
}
keyfa[u]=lastkey;
keycnt[u]=keycnt[fa[u]]+isKey[u];
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==fa[u]) continue;
dfs3(v,lastkey);
}
for(int j=0;j<3;j++) bc[a[u][j]]--;
}
struct Ans{
int col,cnt;
Ans(int _col=0,int _cnt=0):col(_col),cnt(_cnt){}
bool operator<(const Ans &o)const{
return cnt<o.cnt||cnt==o.cnt&&col>o.col;
}
bool operator>(const Ans &o)const{
return cnt>o.cnt||cnt==o.cnt&&col<o.col;
}
};
Ans ans[N][N],buc[M],mx;
void dfs4(int u,int f,int rt){
Ans lastver=mx;
for(int j=0;j<3;j++){
buc[a[u][j]].cnt++;
if(mx<buc[a[u][j]]) mx=buc[a[u][j]];
}
if(isKey[u]) ans[rt][keyid[u]]=mx;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==f) continue;
dfs4(v,u,rt);
}
for(int j=0;j<3;j++) buc[a[u][j]].cnt--; // backtrack
mx=lastver; // backtrack
}
Ans solve(int u,int v){
int lca=LCA(u,v);
Ans ret;
for(int w=u;w!=lca;w=fa[w])
for(int j=0;j<3;j++){
buc[a[w][j]].cnt++;
if(buc[a[w][j]]>ret) ret=buc[a[w][j]];
}
for(int w=v;w!=lca;w=fa[w])
for(int j=0;j<3;j++){
buc[a[w][j]].cnt++;
if(buc[a[w][j]]>ret) ret=buc[a[w][j]];
}
for(int j=0;j<3;j++){
buc[a[lca][j]].cnt++;
if(buc[a[lca][j]]>ret) ret=buc[a[lca][j]];
}
for(int j=0;j<3;j++) buc[a[lca][j]].cnt--;
for(int w=v;w!=lca;w=fa[w])
for(int j=0;j<3;j++) buc[a[w][j]].cnt--;
for(int w=u;w!=lca;w=fa[w])
for(int j=0;j<3;j++) buc[a[w][j]].cnt--;
return ret;
}
int jump(int u,int lca){
for(int i=17;i>=0;i--)
if(keycnt[anc[u][i]]-keycnt[lca]>0) u=anc[u][i];
return u;
}
int cntval(int u,int v,int w){
int lca=LCA(u,v);
int ret=cnt[keyid[u]][w]+cnt[keyid[v]][w]
-cnt[keyid[lca]][w]*2;
for(int j=0;j<3;j++) ret+=(a[lca][j]==w);
return ret;
}
std::vector<int>path;
int main(){
freopen("mode.in","r",stdin);
freopen("mode.out","w",stdout);
memset(head,-1,sizeof head);
scanf("%d",&Type);
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=0;j<3;j++) scanf("%d",&a[i][j]);
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),add(x,y),add(y,x);
dfs1(1,0),dfs2(1,1);
for(int u=1;u<=n;u++) anc[u][0]=fa[u];
for(int i=1;i<18;i++)
for(int u=1;u<=n;u++)
anc[u][i]=anc[anc[u][i-1]][i-1];
for(int p:key) isKey[p]=true;
if(!isKey[1]) isKey[1]=true,key.push_back(1);
for(int p0:key)
for(int p1:key){
if(p1<=p0) continue;
int lca=LCA(p0,p1);
if(!isKey[lca])
isKey[lca]=true,key.push_back(lca);
}
for(int i=0;i<key.size();i++) keyid[key[i]]=i;
dfs3(1,1);
for(int i=1;i<=n;i++) buc[i].col=i;
for(int p:key) dfs4(p,0,keyid[p]);
scanf("%d",&q);
while(q--){
int u,v,lca; Ans res;
scanf("%d%d",&u,&v);
if(Type==1) u^=xorsum,v^=xorsum;
lca=LCA(u,v);
int KEYCNT=keycnt[u]+keycnt[v]-keycnt[lca]*2+isKey[lca];
if(KEYCNT<=1) res=solve(u,v);
else{
if(dfn[u]>dfn[v]) std::swap(u,v);
bool flagu=false,flagv=false;
int keyu=0,keyv=0;
if(keycnt[u]-keycnt[lca]>0) keyu=keyfa[u];
else keyu=jump(v,lca),flagu=true;
if(keycnt[v]-keycnt[lca]>0) keyv=keyfa[v];
else keyv=jump(u,lca),flagv=true;
res=ans[keyid[keyu]][keyid[keyv]];
for(int w=u;w!=lca&&w!=keyu;w=fa[w]){
path.push_back(w);
for(int j=0;j<3;j++){
buc[a[w][j]].cnt++;
Ans qwq=buc[a[w][j]];
qwq.cnt+=cntval(keyu,keyv,a[w][j]);
if(qwq>res) res=qwq;
}
}
for(int w=v;w!=lca&&w!=keyv;w=fa[w]){
path.push_back(w);
for(int j=0;j<3;j++){
buc[a[w][j]].cnt++;
Ans qwq=buc[a[w][j]];
qwq.cnt+=cntval(keyu,keyv,a[w][j]);
if(qwq>res) res=qwq;
}
}
if(flagu)
for(int w=fa[keyu];w!=fa[lca];w=fa[w]){
path.push_back(w);
for(int j=0;j<3;j++){
buc[a[w][j]].cnt++;
Ans qwq=buc[a[w][j]];
qwq.cnt+=cntval(keyu,keyv,a[w][j]);
if(qwq>res) res=qwq;
}
}
if(flagv)
for(int w=fa[keyv];w!=fa[lca];w=fa[w]){
path.push_back(w);
for(int j=0;j<3;j++){
buc[a[w][j]].cnt++;
Ans qwq=buc[a[w][j]];
qwq.cnt+=cntval(keyu,keyv,a[w][j]);
if(qwq>res) res=qwq;
}
}
for(int w:path)
for(int j=0;j<3;j++) buc[a[w][j]].cnt--;
path.clear();
}
xorsum^=res.cnt^res.col;
printf("%d %d\n",res.cnt,res.col);
}
return 0;
}
T4#
咕咕咕
2023.7.20#
T1#
题面
mrsrz 是个珂学家,她正在进行她的珂研项目。
这天,mrsrz 渴了,想喝饮料。她突然想起自己还有一些化学试剂,所以打算自己配饮料。
mrsrz 找到了
化学反应是非常危险的,为了保证安全,mrsrz 给每种试剂规定了用量范围。试剂的用量可以是
现在,mrsrz 想知道,所有配出来总量为
两种饮料不同当且仅当配成两种饮料的试剂中有一者或两者不同,或两种试剂的用量不同。
mrsrz 会问
对于 100% 的数据,
sol
设枚举到
for(int i=l[x];i<=r[x];i++)
for(int j=l[y];j<=r[y];j++) ans[i+j]+=v[x]*v[y];
这样总复杂度是
for(int i=l[x];i<=r[x];i++){
dif[i+l[y]]+=v[x]*v[y];
dif[i+r[y]+1]-=v[x]*v[y];
}
这样总复杂度为
diff[l[i]+l[j]]+=v[i]*v[j];
diff[r[i]+l[j]+1]-=v[i]*v[j];
diff[l[i]+r[j]+1]-=v[i]*v[j];
diff[r[i]+r[j]+2]+=v[i]*v[j];
这样复杂度是
#include<cstdio>
#define int long long
const int M=5010,N=2e7+10;
const int mod=998244353;
int n,m,v[M],l[M],r[M];
int diff[N];
signed main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld%lld%lld",&v[i],&l[i],&r[i]);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
(diff[l[i]+l[j]]+=1ll*v[i]*v[j]%mod)%=mod;
(diff[r[i]+l[j]+1]-=1ll*v[i]*v[j]%mod-mod)%=mod;
(diff[l[i]+r[j]+1]-=1ll*v[i]*v[j]%mod-mod)%=mod;
(diff[r[i]+r[j]+2]+=1ll*v[i]*v[j]%mod)%=mod;
}
for(int i=1;i<N;i++)
(diff[i]+=diff[i-1])%=mod;
for(int i=1;i<N;i++)
(diff[i]+=diff[i-1])%=mod;
for(int i=1,k;i<=m;i++){
scanf("%lld",&k);
printf("%lld\n",diff[k]);
}
return 0;
}
T2#
题面
现在有一张
答案对 inf
。
sol
数据出锅,还没有标明边权范围,也没有说清楚是无向边
边权非负所以只有 inf
。
考虑用并查集将边权为
分别从源点和汇点跑一遍最短路,记作
跑出来
此时 inf
,否则为
这时
为最短路
若 inf
。否则答案为
为最短路
若 inf
。否则答案为
和 都是最短路
若 inf
。否则答案为
#include<queue>
#include<cstdio>
#include<utility>
#include<cstring>
#define int long long
const int M=2023,INF=1e18+7;
const int mod=1e7+7;
int n,m;
struct node{int u,v,w;}g[M];
int fa[M];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
fa[find(x)]=find(y);
}
bool qry(int x,int y){
return find(x)==find(y);
}
bool zero[M];
int head[M],cnte;
struct Edge{int to,next,dis;}e[M<<1];
void add(int u,int v,int w){
e[++cnte]=(Edge){v,head[u],w};
head[u]=cnte;
}
bool vis[2][M],passzero[2][M];
int dis[2][M],f[2][M];
typedef std::pair<int,int> pii;
std::priority_queue<pii,std::vector<pii>,std::greater<pii>>q;
#define qwq std::make_pair
void Dijkstra(int T,int s){
for(int i=1;i<=n;i++) dis[T][i]=INF;
q.push(qwq(dis[T][s]=0,s)),f[T][s]=1;
passzero[T][s]|=zero[s];
while(!q.empty()){
int u=q.top().second; q.pop();
if(vis[T][u]) continue; vis[T][u]=true;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(dis[T][v]>dis[T][u]+e[i].dis){
dis[T][v]=dis[T][u]+e[i].dis,f[T][v]=f[T][u];
passzero[T][v]=passzero[T][u]|zero[v];
q.push(qwq(dis[T][v],v));
}else
if(dis[T][v]==dis[T][u]+e[i].dis){
(f[T][v]+=f[T][u])%=mod;
passzero[T][v]|=passzero[T][u]|zero[v];
}
}
}
}
signed main(){
freopen("impossible.in","r",stdin);
freopen("impossible.out","w",stdout);
for(int i=0;i<M;i++) fa[i]=i;
memset(head,-1,sizeof head);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
int tot=scanf("%lld%lld%lld",&g[i].u,&g[i].v,&g[i].w);
if(tot<3) g[i]=g[i-1];
if(g[i].w==0) merge(g[i].u,g[i].v),zero[find(g[i].u)]=true;
}
for(int i=1;i<=m;i++)
add(find(g[i].u),find(g[i].v),g[i].w),
add(find(g[i].v),find(g[i].u),g[i].w);
Dijkstra(0,find(1)),Dijkstra(1,find(n));
int mndis=dis[0][find(n)];
for(int i=1;i<=m;i++){
int u=g[i].u,v=g[i].v;
if(qry(u,v)){
int f=find(u);
if(dis[0][f]+dis[1][f]==mndis) printf("inf\n");
else printf("0\n");
}else{
u=find(u),v=find(v);
int p1=dis[0][u]+g[i].w+dis[1][v];
int p2=dis[0][v]+g[i].w+dis[1][u];
if(p1!=mndis&&p2!=mndis) printf("0\n");
else if(p2!=mndis){
if(passzero[0][u]||passzero[1][v]) printf("inf\n");
else printf("%lld\n",1ll*f[0][u]*f[1][v]%mod);
}else if(p1!=mndis){
if(passzero[0][v]||passzero[1][u]) printf("inf\n");
else printf("%lld\n",1ll*f[0][v]*f[1][u]%mod);
}else{
if(passzero[0][u]||passzero[1][v]) printf("inf\n");
else if(passzero[0][v]||passzero[1][u]) printf("inf\n");
else printf("%lld\n",(1ll*f[0][u]*f[1][v]%mod+1ll*f[0][v]*f[1][u]%mod)%mod);
}
}
}
return 0;
}
事实上不缩点也行,只要跑最短路的时候记录是否可能经过
#include<queue>
#include<cstdio>
#include<cstring>
#define int long long
const int M=2023,INF=1e18+7;
const int mod=1e7+7;
int n,m;
struct node{int u,v,w;}g[M];
int head[M],cnte;
struct Edge{int to,next,dis;}e[M<<1];
void add(int u,int v,int w){
e[++cnte]=(Edge){v,head[u],w};
head[u]=cnte;
}
typedef std::pair<int,int> pii;
std::priority_queue<pii,std::vector<pii>,std::greater<pii>>q;
#define qwq std::make_pair
bool passzero[2][M],vis[2][M];
int dis[2][M],f[2][M];
void Dijkstra(int T,int s){
for(int i=1;i<=n;i++) dis[T][i]=INF;
q.push(qwq(dis[T][s]=0,s)),f[T][s]=1;
while(!q.empty()){
int u=q.top().second; q.pop();
if(vis[T][u]) continue; vis[T][u]=true;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(dis[T][v]>dis[T][u]+e[i].dis){
dis[T][v]=dis[T][u]+e[i].dis;
passzero[T][v]=passzero[T][u]|(e[i].dis==0);
f[T][v]=f[T][u];
q.push(qwq(dis[T][v],v));
}else
if(dis[T][v]==dis[T][u]+e[i].dis){
passzero[T][v]|=passzero[T][u]|(e[i].dis==0);
(f[T][v]+=f[T][u])%=mod;
}
}
}
}
signed main(){
freopen("impossible.in","r",stdin);
freopen("impossible.out","w",stdout);
memset(head,-1,sizeof head);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
int tot=scanf("%lld%lld%lld",&g[i].u,&g[i].v,&g[i].w);
if(tot!=3) g[i]=g[i-1];
add(g[i].u,g[i].v,g[i].w),add(g[i].v,g[i].u,g[i].w);
}
Dijkstra(0,1),Dijkstra(1,n);
int mn=dis[0][n];
for(int i=1;i<=m;i++){
int u=g[i].u,v=g[i].v;
int p1=dis[0][u]+g[i].w+dis[1][v];
int p2=dis[0][v]+g[i].w+dis[1][u];
if(p1!=mn&&p2!=mn) printf("0\n");
else if(p2!=mn){
if(passzero[0][u]||passzero[1][v]) printf("inf\n");
else printf("%lld\n",1ll*f[0][u]*f[1][v]%mod);
}else if(p1!=mn){
if(passzero[0][v]||passzero[1][u]) printf("inf\n");
else printf("%lld\n",1ll*f[0][v]*f[1][u]%mod);
}else{
if(passzero[0][u]||passzero[1][v]) printf("inf\n");
else if(passzero[0][v]||passzero[1][u]) printf("inf\n");
else printf("%lld\n",(1ll*f[0][u]*f[1][v]%mod+1ll*f[0][v]*f[1][u]%mod)%mod);
}
}
return 0;
}
T3#
题面
xpp 和他的毒瘤朋友在玩游戏。他们两个都是有点聪明。
每次系统会随机出来一个
一开始 xpp 拥有选择权,每轮游戏中,拥有选择权的选手可以选择:
1.将随机出来的数加入自己的得分,然后把选择权交给对手。
2.保留选择权,然后将随机出来的数加给对手。
双方的目标都是让自己的分数减去对手的分数最大。他们会一直进行这个游戏。
由于 xpp 和他的毒瘤朋友有点聪明,所以他们是这么考虑的。假设最后拥有选择权的人比对手得分多
(如果随机出来的数i比d大,那么采用第一种操作,如果比d小,那么采用第二种操作,如果相等那么都可能)
i.e.,假如当前随机出来,若
(也就求在经过无数轮后,xpp 期望比对手多多少分?)
有时候 xpp 还会修改一个
注意: 每次修改不是独立的
sol
设最终答案为
由于随机 「无数」 轮和随机 「无数
令
故考虑先找到整数
令
这时
然后要支持单点修改。
#include<cstdio>
#include<iostream>
#define int long long
const int M=1e6+10,mod=998244353;
inline int qpow(int b,int p){
int ans=1;
for(;p;p>>=1){
if(p&1) ans=ans*b%mod;
b=b*b%mod;
}
return ans;
}
inline int inv(int v){
return qpow(v,mod-2);
}
int m,q,w[M];
#define lb(x) (x&-x)
struct BIT{
int c[M];
void add(int x,int v){
for(;x<=m;x+=lb(x)) c[x]+=v;
}
int qry(int x){
int res=0;
for(;x;x-=lb(x)) res+=c[x];
return res;
}
int qry(int l,int r){
return qry(r)-qry(l-1);
}
}t[2];
int f(int d){
/*
int S=t[0].qry(1,m);
int A=t[0].qry(1,d)-t[0].qry(d+1,m);
int B=t[1].qry(1,d)-t[1].qry(d+1,m);
return A*d-S*d-B;*/
int S=t[0].qry(1,m);
return t[1].qry(1,m)-t[1].qry(1,d)*2+d*(t[0].qry(1,d)*2-S)-S*d;
}
int find_d(){
int L=1,R=m,ans=0,mid;
while(L<=R)
if(f(mid=(L+R)>>1)>=0) ans=mid,L=mid+1;
else R=mid-1;
return ans;
}
signed main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
scanf("%lld",&m);
for(int i=1;i<=m;i++) scanf("%lld",&w[i]);
for(int i=1;i<=m;i++) t[0].add(i,w[i]),t[1].add(i,i*w[i]);
scanf("%lld",&q);
for(int i=1,x,y;i<=q;i++){
scanf("%lld%lld",&x,&y);
t[0].add(x,y-w[x]),t[1].add(x,x*(y-w[x]));
w[x]=y;
int d=find_d();
int A=t[0].qry(1,m)-t[0].qry(1,d)+t[0].qry(d+1,m);
int B=t[1].qry(d+1,m)-t[1].qry(1,d);
printf("%lld\n",B%mod*inv(A%mod)%mod);
}
return 0;
}
T4#
题面
500ms, 64MB.
小 X 最近看了亚瑟王的传说,幻想自己也能成为这样的传奇人物。
在梦中,小 X 看到前方由近及远有
小 X 隐约听见有人在说话:“拔出此石中剑者,即为英格兰之王。”顺着声音的方向,小 X 看见了一把插在石缝中的装饰华美的剑。
“莫非这就是……”小 X 跑过去,恨不得立刻将其拔出。
然而,小 X 接近它时就会被弹开。刚刚的声音再次响起:“看到那些台阶了吗?要想拔出石中剑,首先要跨过那些台阶。你可以选择从任意一个台阶出发,每次向前跨越若干个台阶,但必须保证每次落脚的台阶都高于上一次落脚的台阶。为了展现王的姿态,你要让落脚的次数尽量多。”
这可难不倒小 X,他轻松地完成了任务,步伐在天空中划出了一道优美的弧线。
“最后,你还要在心中默念一个数,才能得到石中剑的认可。记住自己刚才的轨迹了吗?你看那些台阶,其实都是虚幻的,可以任意改变顺序。石中剑需要你回答的是,那些台阶有多少种不同的排列方法,可以用你刚才的轨迹来完成之前的任务呢?”
思考了许久,小 X 身上直冒汗。身为正义使者的你,想要帮助他在梦中成为英格兰之王。为此,你潜入了他的意识,得到了他刚才的轨迹。现在,你必须尽快得到答案,从而放入他的意识,使他通过石中剑的考验。
sol
挺好玩的状压题。
首先要会
int
,考虑三进制状压。每次在序列末尾插入一个数,
由题意,一个序列合法,当最长上升子序列长度恰好为
替换一个数时,将被替换的数代表的数位设为
#include<cstdio>
const int M=14348908;
int pw3[16];
int n,k,a[16],f[M],pre[16];
int bit[16],cntbit;
int bit1[16],cnt1;
int ans;
void getbit(int S){
for(int i=0;i<16;i++) bit[i]=bit1[i]=0;
cntbit=cnt1=0;
while(S)
bit[++cntbit]=S-(S/3)*3,S/=3;
}
int main(){
freopen("sword.in","r",stdin);
freopen("sword.out","w",stdout);
pw3[0]=1;
for(int i=1;i<16;i++) pw3[i]=pw3[i-1]*3;
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++)
scanf("%d",&a[i]),pre[a[i]]=a[i-1];
f[0]=1;
for(int S=0;S<pw3[n];S++){
if(!f[S]) continue;
getbit(S);
int tot=0,mxend=0;
for(int i=1;i<=cntbit;i++){
if(bit[i]) tot++;
if(bit[i]==1) bit1[++cnt1]=mxend=i;
}
if(tot==n) ans+=f[S];
int cur=1;
for(int i=1;i<=n;i++)
if(!bit[i]&&(!pre[i]||bit[pre[i]])){
if(i>mxend){
if(cnt1<k) f[S+pw3[i-1]]+=f[S];
}else{
for(;cur<=cnt1;cur++)
if(i<bit1[cur]&&(i>bit1[cur-1]||cur==1)) break;
if(cur<=cnt1)
f[S+pw3[i-1]+pw3[bit1[cur]-1]]+=f[S];
}
}
}
printf("%d",ans);
return 0;
}
2023.7.21#
T1#
题面
Bessie 正在安排前往牛尼亚的一次出差,那里有
沿着两个城市之间的道路移动需要消耗一天。出差的准备工作十分费钱;旅行
Bessie 在一次出差中最多可以赚到多少哞尼?注意有可能最优方案是 Bessie 不访问城市
sol
设
为了保险
#include<cstdio>
#include<cstring>
const int M=2023;
int max(int A,int B){
return A>B?A:B;
}
int head[M],cnte;
struct Edge{int to,next;}e[M];
void add(int u,int v){
e[++cnte]=(Edge){v,head[u]};
head[u]=cnte;
}
int n,m,c,ans,a[M];
int f[M][M];
int main(){
freopen("time.in","r",stdin);
freopen("time.out","w",stdout);
memset(head,-1,sizeof head);
memset(f,-0x3f,sizeof f),f[0][1]=0;
scanf("%d%d%d",&n,&m,&c);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1,u,v;i<=m;i++)
scanf("%d%d",&u,&v),add(u,v);
for(int T=1;T<=1000;T++)
for(int u=1;u<=n;u++)
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
f[T][v]=max(f[T][v],f[T-1][u]+a[v]);
}
ans=-1e9-7;
for(int T=0;T<=1000;T++) ans=max(ans,f[T][1]-c*T*T);
printf("%d",ans);
return 0;
}
T2#
题面
[USACO20JAN] Farmer John Solves 3SUM G
Farmer John 相信他在算法设计上实现了一个重大突破:他声称他发现了一个 3SUM 问题的近似线性时间算法,这是一个有名的算法问题,尚未发现比运行速度比平方时间明显更优的解法。3SUM 问题的一个形式是:给定一个整数数组
为了测试 Farmer John 的断言,Bessie 提供了一个
不幸的是,Farmer John 刚刚发现了他的算法中的一个错误。他很自信他能修复这个算法,但同时,他请你帮他先通过 Bessie 的测试!
保证对于每个数组元素
sol
对于每个询问
#include<cstdio>
const int M=5050,N=2e6+10;
const int delta=2e6;
int n,q,a[M];
long long f[M][M];
int buc[N+delta];
long long qry(int x0,int y0,int x1,int y1){
return f[x1][y1]-f[x0-1][y1]-f[x1][y0-1]+f[x0-1][y0-1];
}
int main(){
freopen("threesum.in","r",stdin);
freopen("threesum.out","w",stdout);
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
f[i][j]=buc[-a[i]-a[j]+delta];
buc[a[j]+delta]++;
}
for(int j=i+1;j<=n;j++) buc[a[j]+delta]=0;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+f[i][j];
for(int i=1,l,r;i<=q;i++){
scanf("%d%d",&l,&r);
printf("%lld\n",qry(l,l,r,r));
}
return 0;
}
T3#
题面
Bessie 在一个仅允许沿平行于坐标轴方向移动的二维方阵中。她从点
Bessie 是一个过程导向的奶牛,所以她仅允许她自己向上或向右行走,从不向左或向下。类似地,每个跳板也设置为不向左或向下。Bessie 需要行走的距离至少是多少?
sol
一眼二维偏序 DP。
将所有跳板拆成两个点,这样共有
如果
最后要走到
#include<cstdio>
#include<algorithm>
const int M=2e5+10;
int min(int A,int B){
return A<B?A:B;
}
int n,m,f[M],nxt[M];
struct node{int x,y,y0,id;}p[M];
int lsh[M],len;
bool cmp(node A,node B){
return A.x<B.x||A.x==B.x&&A.y<B.y;
}
int c[M];
const int INF=1e9+7;
int lowbit(int x){return x&-x;}
void ins(int x,int y){
for(;x<=len;x+=lowbit(x)) c[x]=min(c[x],y);
}
int qry(int x){
int ret=INF;
for(;x;x-=lowbit(x)) ret=min(ret,c[x]);
return ret;
}
int main(){
freopen("boards.in","r",stdin);
freopen("boards.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&p[i].x,&p[i].y,&p[i+m].x,&p[i+m].y);
lsh[++len]=p[i].y,lsh[++len]=p[i+m].y;
p[i].id=i,p[i+m].id=i+m;
}
std::sort(lsh+1,lsh+1+len);
len=std::unique(lsh+1,lsh+1+len)-lsh-1;
for(int i=1;i<=2*m;i++)
p[i].y0=std::lower_bound(lsh+1,lsh+1+len,p[i].y)-lsh;
std::sort(p+1,p+2*m+1,cmp);
for(int i=1;i<=2*m;i++)
if(p[i].id>m) nxt[p[i].id-m]=i;
int mn=INF;
for(int i=1;i<=2*m;i++){
f[i]=min(f[i],qry(p[i].y0)),ins(p[i].y0,f[i]);
if(p[i].id<=m){
int tmp=nxt[p[i].id];
f[tmp]=min(f[tmp],f[i]+p[i].x+p[i].y-p[tmp].x-p[tmp].y);
}
mn=min(mn,f[i]);
}
printf("%d",mn+2*n);
return 0;
}
T4#
题面
Bessie 成为了一名艺术家,正在创作壁画!她现在正在创作的作品是一个高为
假设方格
求 Bessie 可以创作的不同作品的数量模
sol
根据乘法原理,答案为各联通块答案之积。
可以用并查集来维护联通块。考虑从下向上扫,如果有两个联通块合并,那么新的联通块答案应该是两个联通块的方案之积。如果是通过
#include<cstdio>
#include<cstring>
const int M=1010,N=1e6+10;
const int mod=1e9+7;
int n,m; char mp[M][M];
int get(int x,int y){
return (x-1)*m+y;
}
int fa[N],f[N];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x!=y) fa[y]=x,f[x]=1ll*f[x]*f[y]%mod;
}
const int dx[]={-1,0,0},dy[]={0,-1,1};
bool vis[N];
int main(){
freopen("cave.in","r",stdin);
freopen("cave.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n*m;i++) fa[i]=i,f[i]=1;
for(int i=1;i<=n;i++) scanf(" %s",mp[i]+1);
for(int i=n-1;i>=2;i--){
for(int j=2;j<m;j++)
if(mp[i][j]=='.')
for(int dir=0;dir<3;dir++)
if(mp[i+dx[dir]][j+dy[dir]]=='.')
merge(get(i+dx[dir],j+dy[dir]),get(i,j));
for(int j=2;j<m;j++)
if(mp[i][j]=='.'){
int tmp=find(get(i,j));
if(!vis[tmp])
vis[tmp]=true,(++f[tmp])%=mod;
}
for(int j=2;j<m;j++)
if(mp[i][j]=='.') vis[find(get(i,j))]=false;
}
int ans=1;
for(int i=1;i<=n*m;i++)
if(find(i)==i) ans=1ll*ans*f[i]%mod;
printf("%d",ans);
return 0;
}
「EOF」#
作者:zzxLLL
出处:https://www.cnblogs.com/zzxLLL/p/17544972.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具