CF1239
然后ZUTTER_打的第一场div1以没敢交题 完!美!结!束!!!
A
没有发现性质就找规律海星
我们可以算出一列的贡献:\(g[i][0]\)表示上两个不同,\(g[i][1]\)表示上两个相同就可以互相转移,发现这是0项1项为2的斐波那契数列
有一个发现是如果上一列已经确定了,那么下一列可以和这一列一样或者完全相反其中有在这一列相邻两项都不同的情况下下一列才能与这一列完全相同,且不能有3列完全相同
然后只有2种情况下一列可以完全相同分别编号01也是不能有连续3个数字相同,同上是斐波那契数列
但是直接\(f[n]+f[m]\)还会有01010101和10101010会被算重再减掉2答案就是\(f[n]+f[m]-2\)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int f[400001],n,m;
const int P = 1e9+7;
int main()
{
scanf("%d%d",&n,&m);
f[0]=f[1]=2;
for(int i=2;i<=max(n,m);i++) f[i]=(f[i-1]+f[i-2])%P;
printf("%d",(f[n]+f[m]-2ll+P)%P);
}
B
观察怎样交换会比较优,如果交换并不配对的两个括号必不比交换配对的优
(的值是1,)的值是-1,求前缀和
发现如果已经交换了两个数序列的答案就是序列最小值的出现次数,感性理解一下就是\((....)(...)\)这样互不包含的括号组数
然后就可以做了
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
const int M = 400001;
int n,m,k,a[M],b[M],d[M],mn=0x3f3f3f3f,w=0,s[3][M],pre[M],S[3],w0,w1,res=-1,pp[M];
char c[M];
stack<int> st;
void check(int x,int W0,int W1)
{
if(x>res) res=x, w0=W0, w1=W1;
}
int main()
{
scanf("%d\n%s",&n,c+1);
for(int i=1;i<=n;i++)
{
if(c[i]==')') a[i]=-1;
else a[i]=1;
d[i]=d[i-1]+a[i];
if(d[i]<mn) mn=d[i], w=i+1;
}
if(d[n]!=0)
{
printf("0\n1 1");
return 0;
}
for(int i=w;i<=n;i++)
{
b[++m]=a[i];
pp[m]=i;
}
for(int i=1;i<w;i++)
{
b[++m]=a[i];
pp[m]=i;
}
swap(b,a);
for(int i=1;i<=n;i++)
{
s[0][i]=s[0][i-1];
s[1][i]=s[1][i-1];
s[2][i]=s[2][i-1];
d[i]=d[i-1]+a[i];
if(d[i]==0) s[0][i]++;
if(d[i]==1) s[1][i]++;
if(d[i]==2) s[2][i]++;
}
res=s[0][n]; w1=w0=1;
for(int i=1;i<=n;i++)
{
if(a[i]==1) st.push(i);
else pre[i]=st.top(), st.pop();
}
for(int i=1;i<=n;i++)
{
if(!pre[i]) continue;
int p=pre[i];
S[0]=s[0][i-1]-s[0][p-1];
S[1]=s[1][i-1]-s[1][p-1];
S[2]=s[2][i-1]-s[2][p-1];
if(S[0]+S[1]+S[2]==0) continue;
if(!S[0] && !S[1]) check(S[2]+s[0][n],i,p);
else if(!S[0]) check(S[1],i,p);
else check(S[0],i,p);
}
printf("%d\n%d %d",res,min(pp[w0],pp[w1]),max(pp[w0],pp[w1]));
}
C
这题题意好奇怪啊qaq
如果a正在接水,这时\(b(b>a)\)想喝水,他会因为a不在座位上继续坐在座位上等,接着\(c(c<a)\) 又想喝水,他会去排队,所以接水序列acb
如果a正在接水,这时\(b(b<a)\)想喝水,他会去排队,接着\(c(c<a)\) 又想喝水,他也会去排队,所以接水序列abc
抽象一下就是维护一个队列和一个堆。按照想喝水时间和序号排序后如果下一个人的序号比队列里最后一个人还要小也就是说他的前面每个人都在座位上,他会去排队也就是加入队列;否则他会坐在座位上等到他前面的人都回来且正在等着去排队的人也喝完水才能去接水,把他加入堆
每次清空队列后把堆里的第一个人加入队列重复上面过程即可
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#define LL long long
using namespace std;
const int M = 200001;
priority_queue<int> p;
int n,m,k,ct,f[M];
LL rs[M],T;
struct vv
{
int t,id;
} a[M];
bool cmp(vv a,vv b){return (a.t==b.t)?a.id<b.id : a.t<b.t;}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].t);
a[i].id=i; f[i]=a[i].t;
}
sort(a+1,a+1+n,cmp);
while(ct<n || p.size())
{
int ir;
if(p.size()) ir=-p.top(), p.pop(), T+=m;
else T=(LL)a[++ct].t+m,ir=a[ct].id;
rs[ir]=T;
while(ct<n && T>=a[ct+1].t)
{
++ct;
if(ir<a[ct].id) p.push(-a[ct].id);
else T+=m, rs[a[ct].id]=T, ir=a[ct].id;
}
}
for(int i=1;i<=n;i++) cout<<rs[i]<<' ';
}
D
这题到底是怎么混成div1的D的啊qaqaqaqaq
给定一个n个点的有向图,把n个点分成两组,要求没有第一组指向第二组的边
建一张图和一个反向边的图
如果1在第一组里那么1能连到的点都在第一组,但是如果1能连到整张图那么1就不能再第一组;如果1在第二组那么1在反图中连到的点都在第二组,如果能连满整张图无解
就这样
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int M = 2000001;
int n,m,k,ver[2][M],nex[2][M],head[2][M],cnt,T,x,y,vis[M],ct;
void add(int x,int y)
{
ver[0][++cnt]=y, nex[0][cnt]=head[0][x], head[0][x]=cnt;
ver[1][++cnt]=x, nex[1][cnt]=head[1][y], head[1][y]=cnt;
}
void dfs(int x,int k)
{
if(vis[x]) return ;
vis[x]=1, ct++;
for(int i=head[k][x];i;i=nex[k][i]) if(!vis[ver[k][i]])dfs(ver[k][i],k);
}
int main()
{
scanf("%d",&T);
for(;T;T--)
{
scanf("%d%d",&n,&m); ct=0; cnt=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
if(x!=y)add(x,y);
}
dfs(1,0);
if(ct==n)
{
for(int i=1;i<=n;i++) vis[i]=0;
ct=0;dfs(1,1);
if(ct==n) printf("No\n");
else
{
printf("Yes\n");
printf("%d %d\n",n-ct,ct);
for(int i=1;i<=n;i++) if(!vis[i]) printf("%d ",i);
printf("\n");
for(int i=1;i<=n;i++) if(vis[i]) printf("%d ",i);
printf("\n");
}
}
else
{
printf("Yes\n");
printf("%d %d\n",ct,n-ct);
for(int i=1;i<=n;i++) if(vis[i]) printf("%d ",i);
printf("\n");
for(int i=1;i<=n;i++) if(!vis[i]) printf("%d ",i);
printf("\n");
}
for(int i=1;i<=n;i++) vis[i]=0;
for(int i=1;i<=cnt;i++) nex[0][i]=nex[1][i]=0;
for(int i=1;i<=n;i++) head[0][i]=head[1][i]=0;
}
}
E
由于左上角和右下角必到,所以分别填最小值和次小值
考虑第一行剩下的值从小到大的填,第二行剩下的值从小到大的填
这样的话就可以保证不会在第1个和第n个位置之外的位置向下走
然后问题就变成把2n-2个数分成两组使差最小,背包即可
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const int M = 2000001;
int n,m,k,a[M],S,d[3][M];
LL f[M][26];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n*2;i++) scanf("%d",&a[i]),S+=a[i];
sort(a+1,a+1+n+n); int s=0;
memset(f,-1,sizeof(f));
f[0][0]=0; S-=a[1]+a[2];
S/=2;
for(int i=3;i<=n+n;i++)
{
s+=a[i];
s=min(s,S);
for(int j=s;j>=a[i];j--)
for(int k=min(i-3,(n+n-2)/2);k>=0;k--)
if(f[j-a[i]][k]>=0) f[j][k+1]=f[j-a[i]][k]|(1ll<<i);
}
while(f[S][(n+n-2)/2]==-1) S--;
d[1][1]=a[1];
int ct=1;
LL k=f[S][(n+n-2)/2];
for(int i=3;i<=n+n;i++)
{
if(k&(1ll<<i)) d[1][++ct]=a[i];
}
d[2][n]=a[2];
ct=n;
for(int i=3;i<=n+n;i++)
{
if(!(k&(1ll<<i))) d[2][--ct]=a[i];
}
for(int i=1;i<=n;i++) printf("%d ",d[1][i]);
printf("\n");
for(int i=1;i<=n;i++) printf("%d ",d[2][i]);
}