有一些细节没有想好就放掉了,需要更加严谨一些
有点套路的分块,想了一半就没继续想,且没有想到自己做过的一个简单的询问中的做法,有点失败
比赛链接
简单题
套路题
能够走到至少一个公共点难办,所以考虑容斥,计算 1 1 1 和 2 2 2 不能到达任何一个点的方案数
考虑枚举 1 1 1 和 2 2 2 都无法到达的子集,令其为 S 1 S1 S1,然后枚举 1 1 1 能到达的子集,令其为 S 2 S2 S2,这样可以得出 2 2 2 能够到达的子集 S 3 S3 S3,所以 S 1 S1 S1 与 S 2 , S 3 S2,S3 S2,S3 之间的边是定向了的, S 1 S1 S1 内部的边是可以随便定向的, S 2 , S 3 S2,S3 S2,S3 内部的边可以通过另一个 d p dp dp 求解
考虑令 f [ S ] f[S] f[S] 为 1 1 1 可以到达点集 S S S 的方案数,同样考虑容斥,接下来就很套路了
考虑将 1 , 2 1,2 1,2 的点移出枚举的点击,那么时间复杂度就变成了 O ( 3 n − 2 ) O(3^{n-2}) O(3n−2)
#include
using namespace std;
const int N=16,M=300,P=1e9+7;
int n,m,f[1<<N],g[1<<N],cover[1<<N],lines[1<<N];
int pw2[M];
int e[M],ne[M],h[N],idx;
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
int lowbit(int x){ return x&-x;}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int trans(int S){
for(int i=0;i<=m;i++) if(S==pw2[i]) return i;
}
int main(){
freopen("moviestar.in","r",stdin);
freopen("moviestar.out","w",stdout);
n=read(),m=read();
pw2[0]=1;
for(int i=1;i<=m;i++) pw2[i]=pw2[i-1]*2%P;
memset(h,-1,sizeof(h));
for(int i=1;i<=m;i++){
int x=read(),y=read();x--,y--;
if(x>y) swap(x,y);
add(x,y),add(y,x);
if(x==0&&y==1){ printf("%d\n",pw2[m]);exit(0);}
}
//处理出每个点集内的边的数量
for(int S=1;S<1<<n;S++){
lines[S]=lines[S-lowbit(S)];
int lb=trans(lowbit(S));
for(int i=h[lb];~i;i=ne[i]) if(S>>e[i]&1) lines[S]++;
}
//处理出0可以到达的点的集合的方案数
f[0]=1;
for(int S=1;S<1<<(n-2);S++){
f[S]=pw2[lines[(S<<2)^1]];
// cout<
for(int T=S;;T=(T-1)&S){
if(S!=T) f[S]=(f[S]-1ll*f[T]*pw2[lines[(S^T)<<2]])%P;
if(!T) break;
}
}
//处理出1可以到达的点的集合的方案数
g[0]=1;
for(int S=1;S<1<<(n-2);S++){
g[S]=pw2[lines[(S<<2)^2]];
for(int T=S;;T=(T-1)&S){
if(S!=T) g[S]=(g[S]-1ll*g[T]*pw2[lines[(S^T)<<2]])%P;
if(!T) break;
}
// cout<
}
// cout<
//预处理出每个点集连接的点
for(int S=0;S<1<<n;S++){
for(int i=0;i<n;i++) if(S>>i&1)
for(int j=h[i];~j;j=ne[j]) cover[S]|=1<<e[j];
}
//容斥求答案
int full=(1<<(n-2))-1,ans=0;
for(int S=0;S<1<<(n-2);S++){//0和1都无法到达的点集
int nS=full^S;
for(int T=nS;;T=(T-1)&nS){//T为0能都走到的点
// cerr<
if((cover[(T<<2)^1]&(((nS^T)<<2)^2))>0){
if(!T) break;
continue;
}
// cerr<
ans=(ans+1ll*f[T]*g[nS^T]%P*pw2[lines[S<<2]])%P;
if(!T) break;
}
}
ans=(pw2[m]-ans+P)%P;
printf("%d\n",ans);
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}
//g++ -std=c++11 -O2 -o 1.exe B.cpp
好神啊!
神仙操作1:
根据哥德巴赫猜想,任何一个 ≥ 6 \ge 6 ≥6 的偶数都可以拆分成两个奇质数的和,这在 n ≤ 1 0 7 n\le 10^7 n≤107 时是成立的
所以易得出:
操作 1 1 1:长度为奇质数的段需要操作 1 1 1 次
操作 2 2 2:长度为偶数的段需要操作 2 2 2 次
操作 3 3 3:长度为奇合数的段需要操作 3 3 3 次
神仙操作2:
把原序列异或差分,那么每次就变成只要修改 2 2 2 个数,之间距离为奇质数,使差分序列清 0 0 0
这样最优的操作一定是使奇质数之间的匹配尽量多
为什么?考虑只有操作 3 3 3 可能取代掉操作 1 1 1,但可以发现操作 3 3 3 只会操作至多一次,且一个奇合数必定可以用 3 3 3 次的长度为奇质数的段的取完
然后考虑奇偶连边,跑二分图匹配即可
时间复杂度 O ( k 3 ) O(k^3) O(k3)(二分图匹配绝对跑不满
原题:arc180f
#include
using namespace std;
const int N=4100,V=10000100;
int n,cf[V];
int pr[V],v[V],tot;
bool ispr[V],vis[N];
int nodes[N],cnt,match[N];
int h[N],e[N*N],ne[N*N],idx;
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
void sieve(int n){
for(int i=2;i<=n;i++){
if(!v[i]) v[i]=i,ispr[i]=1,pr[++tot]=i;
for(int j=1;j<=tot&&pr[j]<=n/i;j++){
v[pr[j]*i]=pr[j];
if(v[i]==pr[j]) break;
}
}
ispr[2]=0;
}
bool find(int x){
for(int i=h[x];~i;i=ne[i]){
int y=e[i];
if(vis[y]) continue;
vis[y]=1;
if(!match[y]||find(match[y])){ match[y]=x;return 1;}
}
return 0;
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int main(){
freopen("oatmeal.in","r",stdin);
freopen("oatmeal.out","w",stdout);
sieve(V-1);
n=read();
for(int i=1;i<=n;i++){
int x=read();
cf[x]^=1,cf[x+1]^=1;
}
for(int i=0;i<V;i++) if(cf[i]) nodes[++cnt]=i;
// for(int i=1;i<=cnt;i++) cout<
memset(h,-1,sizeof(h));
int o=0,e=0;
for(int i=1;i<=cnt;i++) o+=nodes[i]&1,e+=~nodes[i]&1;
for(int i=1;i<=cnt;i++) for(int j=1;j<=cnt;j++)
if(nodes[j]>nodes[i]&&ispr[nodes[j]-nodes[i]]) add(i,j+cnt),add(j,i+cnt);
int ans=0;
for(int i=1;i<=cnt;i++){
memset(vis,0,sizeof(vis));
if(find(i)) ans++;
}
ans/=2;
// cout<
printf("%d\n",ans+(o-ans)/2*2+(e-ans)/2*2+((o-ans)&1)*3);
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}
考虑拆分操作贡献
[ x ∼ y ] → [ x ∼ y ] [x\sim y]\to [x\sim y] [x∼y]→[x∼y] 可以拆分成 [ 1 ∼ y ] → [ x ∼ y ] − [ 1 ∼ x − 1 ] → [ x ∼ y ] [1\sim y]\to [x\sim y]\;\;\;-\;\;\;[1\sim x-1]\to [x\sim y] [1∼y]→[x∼y]−[1∼x−1]→[x∼y]
[ 1 ∼ y ] → [ x ∼ y ] [1\sim y]\to [x\sim y] [1∼y]→[x∼y] 是好求的,一遍线段树即可
考虑后面的式子如何求
继续拆分贡献
[ 1 ∼ x − 1 ] → [ x ∼ y ] ⟺ [ 1 ∼ x − 1 ] → [ 1 ∼ y ] − [ 1 ∼ x − 1 ] → [ 1 ∼ x − 1 ] [1\sim x-1]\to [x\sim y]\iff [1\sim x-1]\to [1\sim y]\;\;\;-\;\;\;[1\sim x-1]\to [1\sim x-1] [1∼x−1]→[x∼y]⟺[1∼x−1]→[1∼y]−[1∼x−1]→[1∼x−1]
注意,后面的贡献是可以不区分先后的,即可以先做完所有修改再做询问
这样直接上莫队就可以了
时间复杂度 O ( m k l o g n ) O(m\sqrt klogn) O(mklogn),我只能拿 85 p t s 85pts 85pts
考虑 w x q wxq wxq 的一种序列分块做法,考虑每个修改的贡献,这是可以做到不带 l o g log log 的,但我不想写了,以后有空再补
85 p t s 85pts 85pts 代码:
#include
#define lowbit(x) x&-x
typedef long long LL;
using namespace std;
const int N=200100,M=200100,K=200100;
struct Node{ int op,x,y,v;}q[M];
struct Query{ int l,r,id;}qu[K];
int n,m,k,a[N];
LL res,sum[N],sum2[N],ans[N];
int pos[N];
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
bool cmp(const Query &x,const Query &y){
if(pos[x.l]^pos[y.l]) return pos[x.l]<pos[y.l];
return pos[x.l]&1?x.r<y.r:x.r>y.r;
}
struct SegmentTree{
LL tr1[N],tr2[N];
void ADD(int x,int val){
for(int i=x;i<=n;i+=lowbit(i)) tr1[i]+=val,tr2[i]+=1ll*val*x;
}
void add(int l,int r,int val){
ADD(l,val);
if(r<n) ADD(r+1,-val);
}
LL ASK(int x){
if(!x) return 0;
LL res=0;
for(int i=x;i;i-=lowbit(i)) res+=(x+1)*tr1[i]-tr2[i];
return res;
}
LL ask(int l,int r){ return ASK(r)-ASK(l-1);}
}sg1,sg2,sg3;
void add1(int pos){
if(q[pos].op==1){
res+=sg2.ask(q[pos].x,q[pos].y)*q[pos].v;
sg1.add(q[pos].x,q[pos].y,q[pos].v);
}
}
void del1(int pos){
if(q[pos].op==1){
res-=sg2.ask(q[pos].x,q[pos].y)*q[pos].v;
sg1.add(q[pos].x,q[pos].y,-q[pos].v);
}
}
void add2(int pos){
if(q[pos].op==2){
res+=sg1.ask(q[pos].x,q[pos].y);
sg2.add(q[pos].x,q[pos].y,1);
}
}
void del2(int pos){
if(q[pos].op==2){
res-=sg1.ask(q[pos].x,q[pos].y);
sg2.add(q[pos].x,q[pos].y,-1);
}
}
signed main(){
freopen("milktea.in","r",stdin);
freopen("milktea.out","w",stdout);
n=read(),m=read(),k=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=m;i++){
q[i].op=read(),q[i].x=read(),q[i].y=read();
if(q[i].op==1) q[i].v=read();
}
for(int i=1;i<=n;i++) sg3.add(i,i,a[i]);
for(int i=1;i<=m;i++){
sum[i]=sum[i-1];
if(q[i].op==1) sg3.add(q[i].x,q[i].y,q[i].v);
else sum[i]+=sg3.ask(q[i].x,q[i].y);
}
for(int i=1;i<=m;i++){
sum2[i]=sum2[i-1];
if(q[i].op==1){
sum2[i]+=sg2.ask(q[i].x,q[i].y)*q[i].v;
sg1.add(q[i].x,q[i].y,q[i].v);
}
else{
sum2[i]+=sg1.ask(q[i].x,q[i].y);
sg2.add(q[i].x,q[i].y,1);
}
}
for(int i=1;i<=n;i++) sg1.tr1[i]=sg1.tr2[i]=sg2.tr1[i]=sg2.tr2[i]=0;
for(int i=1;i<=k;i++){
int x=read(),y=read();ans[i]=sum[y]-sum[x-1]+sum2[x-1];
// cout<
qu[i]={x-1,y,i};
}
int B=250;
for(int i=1;i<=n;i++) pos[i]=(i-1)/B+1;
sort(qu+1,qu+k+1,cmp);
for(int cur=1,i=0,j=0;cur<=k;cur++){
while(i<qu[cur].l) add1(++i);
while(i>qu[cur].l) del1(i--);
while(j<qu[cur].r) add2(++j);
while(j>qu[cur].r) del2(j--);
ans[qu[cur].id]-=res;
}
for(int i=1;i<=k;i++) printf("%lld\n",ans[i]);
fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}