题解赛后发。
最后一题没看懂题意就没做
结果发现我第一次理解的题意是对的。。。。。。。
感觉这次题目没有什么思维难度。
ADADISH
签到。
#include<bits/stdc++.h>
using namespace std;
#define N 5010
int n,T,a[N],f[N];
int main(){
scanf("%d",&T);
while(T--){
int s=0;
memset(f,0,sizeof(f));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
s+=a[i];
}
f[0]=1;
for(int i=1;i<=n;i++)
for(int j=s;j>=a[i];j--)
f[j]|=f[j-a[i]];
int ans=1e9;
for(int i=1;i<=s;i++)
if(f[i])
ans=min(ans,max(s-i,i));
printf("%d
",ans);
}
}
RESTORE
签到。
#include<bits/stdc++.h>
using namespace std;
#define N 2000010
int T,n,b[N],ct,p[N],vi[N],cv;
int main(){
scanf("%d",&T);
for(int i=2;i<N;i++){
if(!vi[i])
p[++ct]=i;
for(int j=1;j<=ct&&p[j]*i<N;j++){
vi[i*p[j]]=1;
if(i%p[j]==0)
break;
}
}
while(T--){
cv=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
vi[i]=0;
for(int i=1;i<=n;i++){
if(!vi[b[i]])
vi[b[i]]=++cv;
printf("%d ",p[vi[b[i]]]);
}
puts("");
}
}
FEMA2
把序列从堵住的格子劈开,使用两个set维护磁铁和铁块,贪心的选择距离最远的进行匹配。
#include<bits/stdc++.h>
using namespace std;
#define N 500010
int T,n,k,p[N],ans;
char s[N];
multiset<int>s1,s2;
void gt(int x,int y){
p[x-1]=0;
for(int i=x;i<=y;i++){
p[i]=p[i-1];
if(s[i]==':')
p[i]++;
}
s1.clear();
s2.clear();
for(int i=x;i<=y;i++){
if(s[i]=='I'){
if(s1.empty()){
s2.insert(i+p[i]);
continue;
}
multiset<int>::iterator it=s1.upper_bound(i+p[i]-k-1);
if(it!=s1.end()){
s1.erase(it);
ans++;
}
else
s2.insert(i+p[i]);
}
else if(s[i]=='M'){
if(s2.empty()){
s1.insert(i+p[i]);
continue;
}
multiset<int>::iterator it=s2.upper_bound(i+p[i]-k-1);
if(it!=s2.end()){
s2.erase(it);
ans++;
}
else
s1.insert(i+p[i]);
}
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d%s",&n,&k,s+1);
ans=0;
for(int i=1;i<=n;i++)
if(s[i]!='X'){
int j=i;
while(j<n&&s[j+1]!='X')
j++;
gt(i,j);
i=j;
}
for(int i=1;i<=n;i++)
p[i]=0;
printf("%d
",ans);
}
}
CNDYGAME
按照1的位置分类讨论。
这道简单题搞了我很久。。。。。
#include<bits/stdc++.h>
using namespace std;
#define N 300010
#define int long long
#define mo 1000000007
int T,a[N],s[N],b[N];
signed main(){
scanf("%lld",&T);
while(T--){
int n,q;
scanf("%lld",&n);
int po=0;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
if(a[i]==1){
po=i;
break;
}
if(po==1){
scanf("%lld",&q);
while(q--){
int x,ans;
scanf("%lld",&x);
if(n!=1){
ans=x/n;
if(x%n!=0)
ans++;
if(x%n==1&&x!=1)
ans--;
printf("%lld
",ans%mo);
}
else
printf("%lld
",x%mo);
}
}
else if(po==n||!po){
for(int i=1;i<=n;i++){
if(i!=n){
if(a[i]%2==0){
s[i]=(s[i-1]+a[i])%mo;
b[i]=a[i];
}
else{
s[i]=(s[i-1]+a[i]-1)%mo;
b[i]=a[i]-1;
}
}
else{
if(a[i]%2==1){
s[i]=(s[i-1]+a[i])%mo;
b[i]=a[i];
}
else{
s[i]=(s[i-1]+a[i]-1)%mo;
b[i]=a[i]-1;
}
}
}
scanf("%lld",&q);
while(q--){
int x,ans;
scanf("%lld",&x);
ans=s[n]*((x/n)%mo)%mo;
x%=n;
if(!x){
if(a[n]!=b[n])
printf("%lld
",(ans+1)%mo);
else
printf("%lld
",ans);
}
else{
ans=(ans+s[x])%mo;
if(a[x]!=b[x])
printf("%lld
",(ans+1)%mo);
else
printf("%lld
",ans);
}
}
}
else{
for(int i=1;i<=n;i++){
if(i+1==po){
if(a[i]%2==1){
s[i]=(s[i-1]+a[i])%mo;
b[i]=a[i];
}
else{
s[i]=(s[i-1]+a[i]-1)%mo;
b[i]=a[i]-1;
}
}
else{
if(i==po){
s[i]=s[i-1];
b[i]=0;
}
else if(i!=n){
if(a[i]%2==0){
s[i]=(s[i-1]+a[i])%mo;
b[i]=a[i];
}
else{
s[i]=(s[i-1]+a[i]-1)%mo;
b[i]=a[i]-1;
}
}
else{
if(a[i]%2==1){
s[i]=(s[i-1]+a[i])%mo;
b[i]=a[i];
}
else{
s[i]=(s[i-1]+a[i]-1)%mo;
b[i]=a[i]-1;
}
}
}
}
scanf("%lld",&q);
while(q--){
int x,ans;
scanf("%lld",&x);
ans=s[n]*((x/n)%mo)%mo;
x%=n;
if(!x){
if(a[n]!=b[n])
printf("%lld
",(ans+1)%mo);
else
printf("%lld
",ans);
}
else{
ans=(ans+s[x])%mo;
if(x==po){
if(a[x-1]%2==0)
ans=(ans+2)%mo;
printf("%lld
",ans);
}
else{
if(a[x]!=b[x])
printf("%lld
",(ans+1)%mo);
else
printf("%lld
",ans);
}
}
}
}
for(int i=1;i<=n+5;i++)
a[i]=s[i]=b[i]=0;
}
}
UNSQUERS
某个原题的加强版。
把一个点向它右边第一个>它的点连边,则形成了一棵树。
在一次询问内把区间所有点标记。
考虑扫描线,扫到i的时候处理以i为右端点的询问。
注意到一个点一定和一个被标记的叶子节点连通(就是只能通过标记的点到达)。
考虑维护一个点能够向上跳跳到标记点的最多次数(a),则答案就是a在区间[l,r]的最大值。
如果把树dfs一下,则每次对答案的贡献就是个区间+,使用线段树维护。
在线把线段树可持久化一下。
#include<bits/stdc++.h>
using namespace std;
#define N 200010
int n,m,a[N],bt[N],v[N*2],nxt[N*2],h[N],ec,ans[N],mn[N],rt[N];
void add(int x,int y){v[++ec]=y;nxt[ec]=h[x];h[x]=ec;}
void gt(int x,int y){for(;x;x-=x&-x)bt[x]=min(bt[x],y);}
int q(int x){int r=1e9;for(;x<100010;x+=x&-x)r=min(r,bt[x]);return r;}
struct tr{
int tg[N*30],s[N*30],lc[N*30],rc[N*30],ct;
void add(int &o,int p,int l,int r,int x,int y,int z){
o=++ct;
s[o]=s[p];
tg[o]=tg[p];
lc[o]=lc[p];
rc[o]=rc[p];
if(r<x||y<l)return;
if(x<=l&&r<=y){
tg[o]+=z;
s[o]+=z;
return;
}
int md=(l+r)/2;
add(lc[o],lc[p],l,md,x,y,z);
add(rc[o],rc[p],md+1,r,x,y,z);
s[o]=max(s[lc[o]],s[rc[o]])+tg[o];
}
int q(int o,int l,int r,int x,int y,int va){
if(r<x||y<l)return 0;
if(x<=l&&r<=y)return s[o]+va;
int md=(l+r)/2;
return max(q(lc[o],l,md,x,y,va+tg[o]),q(rc[o],md+1,r,x,y,va+tg[o]));
}
}sv;
void dfs(int x){
mn[x]=x;
for(int i=h[x];i;i=nxt[i]){
dfs(v[i]);
mn[x]=min(mn[x],mn[v[i]]);
}
}
int main(){
//freopen("exchange.in","r",stdin);
//freopen("exchange.out","w",stdout);
int t;
scanf("%d%d%d",&n,&m,&t);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
memset(bt,63,sizeof(bt));
n++;a[n]=100005;
for(int i=n;i>=1;i--){
int v=q(a[i]+1);
if(i!=n)add(v,i);
gt(a[i],i);
}
dfs(n);
int p=1;
for(int i=1;i<=n;i++)
sv.add(rt[i],rt[i-1],1,n,mn[i],i,1);
int la=0;
for(int i=1;i<=m;i++){
int l,r;
scanf("%d%d",&l,&r);
l=(l+t*la-1)%(n-1)+1;
r=(r+t*la-1)%(n-1)+1;
if(l>r)
swap(l,r);
printf("%d
",la=sv.q(rt[r],1,n,l,r,0));
}
}
SCALSUM
显然可以树上莫队,但是时间很卡。
考虑一种常数更小的方法。
设(s_i)为深度为(i)的点的个数。
维护一个点(i)跳到上面第一次跳到(<sqrt{n},geq sqrt{n})的点(f_i,g_i)。
暴力跳(g),维护(s_{i,j,k})表示深度为(k)的点,(i,j)的贡献。
然后(s_{i,j,k})能够通过(s_{i,j,l})((l)为(k)上面第一个(s)值(<sqrt{n})的点)轻松得来。
#include<bits/stdc++.h>
#define int unsigned int
//#pragma GCC optimize(3)
using namespace std;
#define N 600010
int n,q,w[N],v[N],nxt[N],h[N],ec,d[N],cc[N],id[N],ans[N],f[N],tp[600][600],nw[600][600],tt[N],ff[N],bs,g[N];
void add(int x,int y){
v[++ec]=y;
nxt[ec]=h[x];
h[x]=ec;
}
struct no{
int x,y,id;
};
vector<no>vc[N];
vector<int>dc[N];
int rd(){
int x=0;
char c=getchar();
while(!isdigit(c))
c=getchar();
while(isdigit(c)){
x=x*10u+c-'0';
c=getchar();
}
return x;
}
void d1(int x,int fa){
dc[d[x]].push_back(x);
ff[x]=fa;
cc[d[x]]++;
for(int i=h[x];i;i=nxt[i])
if(v[i]!=fa){
d[v[i]]=d[x]+1;
d1(v[i],x);
}
}
void d2(int x,int fa){
for(int i=h[x];i;i=nxt[i])
if(v[i]!=fa){
if(cc[d[v[i]]]<=bs)
f[v[i]]=f[x];
else
f[v[i]]=v[i];
if(cc[d[v[i]]]>bs)
g[v[i]]=g[x];
else
g[v[i]]=v[i];
d2(v[i],x);
}
}
signed main(){
//freopen("r.in","r",stdin);
//freopen("w2.out","w",stdout);
n=rd();
q=rd();
for(int i=1;i<=n;i++)
w[i]=rd();
for(int i=1;i<n;i++){
int x=rd(),y=rd();
add(x,y);
add(y,x);
}
bs=sqrt(n);
g[1]=1;
d1(1,0);
d2(1,0);
for(int i=1;i<=q;i++){
int x=rd(),y=rd();
if(g[x])
vc[d[g[x]]].push_back((no){g[x],g[y],i});
x=f[x];
y=f[y];
while(x){
ans[i]+=w[x]*w[y];
x=f[ff[x]];
y=f[ff[y]];
}
}
for(int i=0;i<=n;i++)
if(cc[i]<=bs){
int ct=0,s=dc[i].size();
for(int j=0;j<s;j++){
tt[++ct]=dc[i][j];
id[dc[i][j]]=ct;
}
for(int j=1;j<=ct;j++)
for(int k=1;k<=ct;k++){
int a=tt[j],b=tt[k],c=g[ff[a]],d=g[ff[b]];
nw[id[a]][id[b]]=w[a]*w[b]+tp[id[c]][id[d]];
}
for(no x:vc[i])
ans[x.id]+=nw[id[x.x]][id[x.y]];
for(int j=1;j<=ct;j++)
for(int k=1;k<=ct;k++){
tp[j][k]=nw[j][k];
nw[j][k]=0;
}
}
for(int i=1;i<=q;i++)
printf("%u
",ans[i]);
}
CHEFSSM
(sum E(p)*i=sum E(p>=i)),其中(p)为一个局面能够转到正确位置的最小步数。
实际上我们要求的是对于(0leq xleq p)的(x),((2x+v_1)(2x+v_2)...(2x+v_n))的和。
把这个式子用分治(fft)展开,发现我们要求若干个底数相同但是幂数不同的自然数幂和。
使用伯努利数计算。求自然数幂和的公式也可以用卷积优化。
#include<bits/stdc++.h>
using namespace std;
#define mo 998244353
#define N 300010
#define int long long
#define ll unsigned long long
#define pl vector<int>
int qp(int x,int y){
int r=1;
for(;y;y>>=1,x=1ll*x*x%mo)
if(y&1)r=1ll*r*x%mo;
return r;
}
int n,k,rev[N],v,c,le,w[N];
ll b[N];
void deb(pl x){
for(int i:x)cout<<i<<' ';
puts("");
}
void init(int n){
v=1;
le=0;
while(v<n)le++,v*=2;
for(signed i=0;i<v;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(le-1));
int g=qp(3,(mo-1)/v);
w[v/2]=1;
for(int i=v/2+1;i<v;i++)
w[i]=1ull*w[i-1]*g%mo;
for(signed i=v/2-1;~i;i--)
w[i]=w[i*2];
}
void fft(int v,pl &a,int t){
static unsigned long long b[N];
int s=le-__builtin_ctz(v);
for(int i=0;i<v;i++)
b[rev[i]>>s]=a[i];
int c=0;
w[0]=1;
for(signed i=1;i<v;i*=2,c++)
for(signed r=i*2,j=0;j<v;j+=r)
for(signed k=0;k<i;k++){
int tx=b[j+i+k]*w[k+i]%mo;
b[j+i+k]=b[j+k]+mo-tx;
b[j+k]+=tx;
}
for(int i=0;i<v;i++)
a[i]=b[i]%mo;
if(t==0)return;
int iv=qp(v,mo-2);
for(signed i=0;i<v;i++)
a[i]=1ull*a[i]*iv%mo;
a.resize(v);
reverse(a.begin()+1,a.end());
}
pl operator *(pl x,pl y){
int s=x.size()+y.size()-1;
init(s);
x.resize(v);
y.resize(v);
fft(v,x,0);
fft(v,y,0);
for(int i=0;i<v;i++)
x[i]=x[i]*y[i]%mo;
fft(v,x,1);
x.resize(s);
return x;
}
void inv(int n,pl &b,pl &a){
if(n==1){
b[0]=qp(a[0],mo-2);
return;
}
inv((n+1)/2,b,a);
static pl c;
init(n*2);
c.resize(v);
b.resize(v);
for(int i=0;i<n;i++)
c[i]=a[i];
fft(v,c,0);
//deb(c);
fft(v,b,0);
//deb(b);
for(int i=0;i<v;i++)
b[i]=1ll*(2ll-1ll*c[i]*b[i]%mo+mo)%mo*b[i]%mo;
//deb(b);
fft(v,b,1);
//deb(b);
b.resize(n);
//deb(b);
}
void ad(pl &x,pl y,int l){
x.resize(max((int)x.size(),(int)y.size()+l));
for(int i=0;i<y.size();i++)
x[i+l]=(x[i+l]+y[i])%mo;
}
pl operator +(pl x,pl y){
ad(x,y,0);
return x;
}
pl iv(pl x){
pl y;
int n=x.size();
y.resize(n);
inv(n,y,x);
y.resize(n);
return y;
}
int a[N],T,B[N],va[N];
pl fz(int l,int r){
if(l==r){
pl va;
va.resize(2);
va[1]=mo-2;
va[0]=a[l]+1;
return va;
}
int md=(l+r)/2;
return fz(l,md)*fz(md+1,r);
}
void cal(int v,int x){
pl a,b;
a.resize(x+2);
b.resize(x+2);
va[0]=v;
int vv=1;
for(int i=0;i<=x+1;i++){
a[i]=B[i]*qp(vv,mo-2)%mo;
vv=vv*(i+1)%mo;
}
vv=1;
for(int i=0;i<=x+1;i++){
b[i]=qp(v+1,i+1)*qp(vv,mo-2)%mo;
vv=vv*(i+2)%mo;
}
a=a*b;
vv=1;
for(int i=1;i<=x;i++){
va[i]=vv*a[i]%mo;
vv=vv*(i+1)%mo;
}
va[0]=v;
}
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld",&n);
int mn=1e18,vv=1;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
mn=min(mn,a[i]);
va[i]=a[i];
vv=vv*(a[i]+1)%mo;
}
pl y;
y.resize(n+1);
int x=1;
for(int i=0;i<=n;i++){
x=x*(i+1)%mo;
y[i]=qp(x,mo-2)%mo;
}
y=iv(y);
x=1;
for(int i=0;i<=n;i++){
B[i]=y[i]*x%mo;
x=x*(i+1)%mo;
}
B[0]=1;
pl z=fz(1,n);
cal((mn+1)/2,n);
int ans=0;
for(int i=0;i<=n;i++)
ans=(ans+va[i]*z[i]%mo)%mo;
printf("%lld
",ans*qp(vv,mo-2)%mo);
}
}
RB2CNF
显然我们求出一个合法的染色方案后就是个经典的二元关系网络流。
然后求出合法的染色方案可以使用二分图染色。