冲刺国赛模拟 5
今天上午给我整不会了,开着空调结果冻的要死,这还是外边三十多度的情况下。结果一上午两眼发黑打题,心态相当爆炸。
题目名称好像是 qlr 洛谷主页。
今晚九点
简单题。为啥赛时没人做。
在每个位置有意义的走法显然只有两种:用 \(2\) 步走到相邻一格,或者用 \(1\) 步走到头。那每个点最多 \(8\) 条边连出去,直接跑最短路就行。
int n,m,num,id[1010][1010];
char s[1010][1010];
struct node{
int v,w,next;
}edge[8000010];
int t,head[1000010];
void add(int u,int v,int w){
edge[++t].v=v;edge[t].w=w;edge[t].next=head[u];head[u]=t;
}
struct stu{
int x,w;
bool operator<(const stu&s)const{
return w>s.w;
}
};
priority_queue<stu>q;
int dis[1000010];
bool vis[1000010];
void dijkstra(int st){
memset(dis,0x3f,sizeof(dis));
dis[st]=0;q.push({st,0});
while(!q.empty()){
int x=q.top().x;q.pop();
if(!vis[x]){
vis[x]=true;
for(int i=head[x];i;i=edge[i].next){
if(dis[edge[i].v]>dis[x]+edge[i].w){
dis[edge[i].v]=dis[x]+edge[i].w;
if(!vis[edge[i].v])q.push({edge[i].v,dis[edge[i].v]});
}
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",s[i]+1);
for(int j=1;j<=m;j++)id[i][j]=++num;
}
for(int i=2;i<n;i++){
int pre;
for(int j=2;j<m;j++){
if(s[i][j]=='#')continue;
if(s[i][j]=='.'&&s[i][j-1]=='#')pre=j;
if(j^pre)add(id[i][j],id[i][pre],1);
if(s[i][j]=='.'&&s[i][j-1]=='.')add(id[i][j],id[i][j-1],2);
}
for(int j=m-1;j>=2;j--){
if(s[i][j]=='#')continue;
if(s[i][j]=='.'&&s[i][j+1]=='#')pre=j;
if(j^pre)add(id[i][j],id[i][pre],1);
if(s[i][j]=='.'&&s[i][j+1]=='.')add(id[i][j],id[i][j+1],2);
}
}
for(int j=2;j<m;j++){
int pre;
for(int i=2;i<n;i++){
if(s[i][j]=='#')continue;
if(s[i][j]=='.'&&s[i-1][j]=='#')pre=i;
if(i^pre)add(id[i][j],id[pre][j],1);
if(s[i][j]=='.'&&s[i-1][j]=='.')add(id[i][j],id[i-1][j],2);
}
for(int i=n-1;i>=2;i--){
if(s[i][j]=='#')continue;
if(s[i][j]=='.'&&s[i+1][j]=='#')pre=i;
if(i^pre)add(id[i][j],id[pre][j],1);
if(s[i][j]=='.'&&s[i+1][j]=='.')add(id[i][j],id[i+1][j],2);
}
}
int x1,y1,x2,y2;scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
dijkstra(id[x1][y1]);
if(dis[id[x2][y2]]==0x3f3f3f3f)dis[id[x2][y2]]=-1;
printf("%d\n",dis[id[x2][y2]]);
return 0;
}
小王唱歌
赛时冻的神志不清导致完美错过正解。
首先简单容斥一下,枚举边集。然后就变成了算每个连通块最小值的积。设 \(dp_{x,i}\) 为 \(x\) 子树,\(x\) 所在连通块最小值为 \(i\) 的答案,再设个 \(g\) 为不算 \(x\) 连通块的答案。转移很简单,考虑加不加入连通块:
只有后缀和,冲一发线段树合并就行了。
因为 pushup 没取模调了一个小时,警钟长鸣。
const int mod=998244353;
int n,cnt,b[500010],lsh[500010];
struct gra{
int v,next;
}edge[1000010];
int tot,head[500010];
void add(int u,int v){
edge[++tot].v=v;edge[tot].next=head[u];head[u]=tot;
}
struct node{
#define lson tree[rt].ls
#define rson tree[rt].rs
int ls,rs,f,g,lz;
}tree[500010<<5];
int t,rt[500010];
void pushup(int rt){
tree[rt].f=(tree[lson].f+tree[rson].f)%mod;
tree[rt].g=(tree[lson].g+tree[rson].g)%mod;
}
void pushtag(int rt,int val){
tree[rt].f=1ll*tree[rt].f*val%mod;
tree[rt].g=1ll*tree[rt].g*val%mod;
tree[rt].lz=1ll*tree[rt].lz*val%mod;
}
void pushdown(int rt){
if(tree[rt].lz!=1){
if(lson)pushtag(lson,tree[rt].lz);
if(rson)pushtag(rson,tree[rt].lz);
tree[rt].lz=1;
}
}
void update(int &rt,int L,int R,int pos){
if(!rt)rt=++t,tree[rt].lz=1;
if(L==R){
tree[rt].g=1;tree[rt].f=lsh[L];return;
}
int mid=(L+R)>>1;
if(pos<=mid)update(lson,L,mid,pos);
else update(rson,mid+1,R,pos);
pushup(rt);
}
int merge(int x,int y,int l,int r,int sumx,int sumy,int val){
if(!x&&!y)return 0;
if(!x){
pushtag(y,(mod-sumx)%mod);
return y;
}
if(!y){
pushtag(x,(val-sumy+mod)%mod);
return x;
}
if(l==r){
int ret=tree[x].g;
tree[x].g=1ll*tree[x].g*val%mod;
tree[x].g=(tree[x].g-1ll*ret*sumy%mod+mod)%mod;
tree[x].g=(tree[x].g-1ll*tree[y].g*sumx%mod+mod)%mod;
tree[x].g=(tree[x].g-1ll*ret*tree[y].g%mod+mod)%mod;
tree[x].f=1ll*tree[x].g*lsh[l]%mod;
return x;
}
pushdown(x);pushdown(y);
int mid=(l+r)>>1;
tree[x].ls=merge(tree[x].ls,tree[y].ls,l,mid,(sumx+tree[tree[x].rs].g)%mod,(sumy+tree[tree[y].rs].g)%mod,val);
tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,r,sumx,sumy,val);
pushup(x);
return x;
}
void dfs(int x,int f){
update(rt[x],1,cnt,b[x]);
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
dfs(edge[i].v,x);
rt[x]=merge(rt[x],rt[edge[i].v],1,cnt,0,0,tree[rt[edge[i].v]].f);
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&b[i]),lsh[i]=b[i];
sort(lsh+1,lsh+n+1);
cnt=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;i++)b[i]=lower_bound(lsh+1,lsh+cnt+1,b[i])-lsh;
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,0);
printf("%d\n",tree[rt[1]].f);
return 0;
}
不见不散
超级结论题。
首先如果操作数和逆序对数奇偶性不同显然无解。剩下的猜一下有解,开始构造。
设当前每个人有的礼物的排列为 \(p\),首先我们有显然的方法去掉一个 \(p_i\neq a_i\) 的数:把它和别的都交换一遍,并把 \(p_j=a_i\) 的 \(j\) 放到最后交换。这样去掉所有 \(p_i\neq a_i\) 的数,剩下的数都是 \(p_i=a_i\)。
考虑所有剩下的数,此时逆序对数为 \(0\),因此剩下的数个数一定满足 \(\bmod 4\equiv 0/1\)。那么四个四个去掉就行。
首先我们有方法用完两个数的操作次数并交换它们:设它们为 \(a,b\),先使 \(a\) 和除了 \(b\) 以外的所有数交换,然后交换 \(a,b\),最后把 \(b\) 按照 \(a\) 的操作顺序反过来操作。这样只有这两个数交换了,别的数都没有变化。那么四个数互相交换的方法是容易手模得到的,四个四个去掉就行了。
int n,cnt,p[1010],a[1010];
vector<int>S;
void get(int x,int y){
if(x>y)swap(x,y);
swap(p[x],p[y]);printf("%d %d\n",x,y);
}
void del(int x){
for(vector<int>::iterator it=S.begin();it!=S.end();++it){
if(*it==x){
S.erase(it);break;
}
}
int pos;
for(int i:S)if(p[i]==a[x]){
pos=i;break;
}
for(int i:S)if(pos!=i)get(x,i);
get(x,pos);
}
void solve(int x,int y){
for(int i:S)get(x,i);
get(x,y);
reverse(S.begin(),S.end());
for(int i:S)get(y,i);
}
void calc(){
int a=S.back();S.pop_back();
int b=S.back();S.pop_back();
int c=S.back();S.pop_back();
int d=S.back();S.pop_back();
solve(a,b);solve(c,d);
get(a,c);get(b,d);get(a,d);get(b,c);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),p[i]=i,S.push_back(i);
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i]>a[j])cnt++;
}
}
if((cnt^(n*(n-1)>>1))&1){
puts("NO");return 0;
}
puts("YES");
while(1){
bool jud=true;
for(int i:S){
if(p[i]!=a[i]){del(i);jud=false;break;}
}
if(jud)break;
}
while(S.size()>1)calc();
return 0;
}