Cover
题意:
要求遍历一个无向图的所有边,一条边只能经过一次,问最少需要几次才能完成?最后输出每次遍历的路径。
分析:
感觉dls的想法是真的灵性,orz,贴一篇个人感觉写的好的博客吧,觉得大佬说的很清楚。
参考资料:大佬博客
代码:

#include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long #define cls(x) memset(x,0,sizeof(x)) #define clslow(x) memset(x,-1,sizeof(x)) const int maxn=1e5+100; int n,m; int cnt,tot; bool vis[maxn]; int head[maxn],deg[maxn]; vector<int>ans[maxn]; struct Edge { bool isused; int v,id,nex; }; Edge edge[maxn<<2]; void init() { cnt=tot=0; cls(vis); cls(deg); clslow(head); } void addedge(int u,int v,int id) { edge[tot].v=v; edge[tot].id=id; edge[tot].nex=head[u]; edge[tot].isused=false; head[u]=tot++; } void dfs(int u) { vis[u]=true; for(int i=head[u];i!=-1;i=edge[i].nex){ if(edge[i].isused) continue; int v=edge[i].v,id=edge[i].id; edge[i].isused=edge[i^1].isused=true; dfs(v); if(id) ans[cnt].push_back(-id); else cnt++; } } void print() { printf("%d ",cnt); for(int i=1;i<=cnt;i++){ int sz=ans[i].size(); printf("%d",sz); for(int j=0;j<sz;j++){ printf(" %d",ans[i][j]); } printf(" "); ans[i].clear(); } } int main() { // freopen("in.txt","r",stdin); while(scanf("%d%d",&n,&m)!=EOF) { init(); for(int i=1;i<=m;i++){ int u,v; scanf("%d%d",&u,&v); deg[u]++;deg[v]++; addedge(u,v,i);addedge(v,u,-i); } int last=-1; for(int i=1;i<=n;i++){ if(deg[i]&1){ if(last==-1) last=i; else{ addedge(last,i,0); addedge(i,last,0); last=-1; } } } for(int i=1;i<=n;i++){ if(!vis[i]&&(deg[i]&1)){ cnt++; dfs(i); cnt--; } } for(int i=1;i<=n;i++){ if(!vis[i]&°[i]){ cnt++; dfs(i); } } print(); } return 0; }
Game
题意:
Alice和Bob先后手玩游戏,每次可以从一个集合中拿出一个数(初始为1~n),拿出之后,这个数的所有因数都会从这个集合中消失,谁没有数可拿时,就输了。问Alice是否能赢?
分析:
如果先手拿x为必胜态,那么Alice一定能赢;如果先手拿x为必输态,那么Alice先手拿1,则必输态转移给Bob,所以Alice一定能赢。
代码:

#include <cmath> #include <vector> #include <stdio.h> #include <iostream> using namespace std; #define ll long long const int maxn=1e5+100; int main() { // freopen("in.txt","r",stdin); int n; while(scanf("%d",&n)!=EOF) { printf("Yes "); } return 0; }
Hack It
题意:
要求构造一个n*n的矩阵,要求不存在一个子矩阵四个角都为1,矩阵的大小1<=n<=2000,矩阵中1的个数大于等于85000。
分析:
n=3,1的位置在j上依次+0,1,2,3……
对于上图,我们首先看一下,上面的矩阵是怎么构造出来的。
1.首先对于第i块,第一列1的位置为第i个。
2.对于第i块,我们也可以看成有n个小块,对于第i块中的第j小块(j>1),它的第k行中1的位置是由前一小块中1的位置+(k-1)得到,也就是说由第一小块中1的位置+(k-1)*(j-1)。
因为矩阵最大为2000,所以我们构造矩阵的大小sz就取2000,为了构造时不会重复,所以n采用质数,最接近sqrt(sz)=sqrt(2000)的质数为47.
代码:

#include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long #define cls(x) memset(x,0,sizeof(x)) #define clslow(x) memset(x,-1,sizeof(x)) const int maxn=3000; int n=47,sz=2000; bool a[maxn][maxn]; int main() { // freopen("in.txt","r",stdin); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ for(int k=0;k<n;k++){ //i*n+j:第i+1块第j+1行 //k*n:第k+1小块 //(j*k+i)%n:第(j*k+i)%n列 a[i*n+j][k*n+(j*k+i)%n]=1; } } } printf("%d ",sz); for(int i=0;i<sz;i++){ for(int j=0;j<sz;j++){ printf("%d",a[i][j]); } printf(" "); } return 0; }
Matrix
题意:
给出n*m的矩阵,每个格子可以涂白色和黑色,现在问至少有row行col列为黑色的涂色方案共有多少种?
分析:
很容易看出这是一个容斥原理的题,然后……还是乖乖看dls吹水吧。对于这题总体思路是,分别求出行和列的容斥系数(我是这么理解的),然后最后一个容斥公式累加得到答案,具体分析看代码注释吧。
代码:

#include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long #define cls(x) memset(x,0,sizeof(x)) #define clslow(x) memset(x,-1,sizeof(x)) const int maxn=3000+100; const int mod=998244353; int n,m,row,col; int x[maxn*maxn],c[maxn][maxn]; int a[maxn],b[maxn],f[maxn][maxn]; void init() { //组合数 for(int i=0;i<maxn;i++){ c[i][0]=1;c[i][i]=1; for(int j=1;j<i;j++){ c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod; } } //2的幂 x[0]=1; for(int i=1;i<maxn*maxn;i++){ x[i]=(x[i-1]<<1)%mod; } } int main() { // freopen("in.txt","r",stdin); init(); while(scanf("%d%d",&n,&m)!=EOF) { scanf("%d%d",&row,&col); //a[i]:我把它理解为容斥系数。 //我们知道容斥原理是由单个集合的大小减去两个集合的交集,加上三个集合的交集…… //这里也是类似,因为最小为i行,所以i行对应单个集合,它的容斥系数就为1。当行数为i+1时, //因为下面计算f[k][l]时空白格子是随机染色,所以行数低的与行数高的有重复的染色方案, //根据容斥原理我们需要减去任意两个集合的交集部分,所以对应的容斥系数减去交集的个数。 a[row]=1; for(int i=row+1;i<=n;i++){ a[i]=1; for(int j=row;j<i;j++){ a[i]=(a[i]-(ll)a[j]*c[i][j])%mod; } } //含义同上 b[col]=1; for(int i=col+1;i<=m;i++){ b[i]=1; for(int j=col;j<i;j++){ b[i]=(b[i]-(ll)b[j]*c[i][j])%mod; } } //不同的行列的方法数之间的涂色方案有重复 //有k行l列全为黑色的涂色方案 for(int i=row;i<=n;i++){ for(int j=col;j<=m;j++){ //涂黑k行l列后,剩下(n-k)*(m-l)格子可以为黑可以为白, //每个格子有2种可能,总共有2^((n-k)*(m-l)) f[i][j]=(ll)c[n][i]*c[m][j]%mod*x[(n-i)*(m-j)]%mod; } } ll ans=0; for(int i=row;i<=n;i++){ for(int j=col;j<=m;j++){ ////容斥原理计算出所有的可能性 ans=(ans+(ll)a[i]*b[j]%mod*f[i][j])%mod; } } //+mod防止负数 ans=(ans+mod)%mod; printf("%lld ",ans); } return 0; }
Naive Operations
题意:
给出b数组,和q次操作。如果为add l r操作,则把a数组【l,r】区间的值都加上一,query l r操作询问【l,r】区间a【i】/b【i】(向下取整)的和。
分析:
利用线段树存下区间的最大a[i]和最小b[i]值,当a[i]>=b[i]时,说明c[i]的值可能需要加一(c[i]=a[i]/b[i]),为什么说是可能呢?因为如果两个数的a[i],b[i]值分别为(1,2),(2,3),这时虽然线段树没有更新,但是它会往下去搜索,就会导致时间的浪费,sum存储对应区间的c[i]之和,具体看代码注释。
代码:

#include <cmath> #include <vector> #include <stdio.h> #include <iostream> using namespace std; #define ll long long #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int maxn=1e5+100; int n,q,l,r,c; char s[10]; ll b[maxn]; //add[rt]:延迟标记,rt区间的a[i]值需要加上add[rt] //sum[rt]:ai/bi的区间和 //minval[rt]:rt区间的最小b[i]值 //maxval[rt]:rt区间的最大a[i]值 ll sum[maxn<<2],add[maxn<<2],minval[maxn<<2],maxval[maxn<<2]; void PushUp(int rt) { sum[rt]=sum[rt<<1]+sum[rt<<1|1]; minval[rt]=min(minval[rt<<1],minval[rt<<1|1]); maxval[rt]=max(maxval[rt<<1],maxval[rt<<1|1]); } void PushDown(int rt) { if(add[rt]){ add[rt<<1]+=add[rt]; add[rt<<1|1]+=add[rt]; maxval[rt<<1]+=add[rt]; maxval[rt<<1|1]+=add[rt]; add[rt]=0; } } void build(int l,int r,int rt) { add[rt]=0; if(l==r){ sum[rt]=0; maxval[rt]=0; minval[rt]=b[l]; return; } int m=(l+r)>>1; build(lson); build(rson); PushUp(rt); } void update(int L,int R,int c,int l,int r,int rt) { if(L<=l&&r<=R){ maxval[rt]+=c; if(maxval[rt]<minval[rt]){ add[rt]+=c; return; } if(l==r&&maxval[rt]>=minval[rt]){ sum[rt]++; //第i次贡献时,maxval[rt]的值为i*b[l] //所以每次maxval[rt]贡献后,minval[rt]的值需要加上b[l] minval[rt]+=b[l]; return; } } PushDown(rt); int m=(l+r)>>1; if(L<=m) update(L,R,c,lson); if(R>m) update(L,R,c,rson); PushUp(rt); } ll query(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R){ return sum[rt]; } PushDown(rt); ll ans=0; int m=(l+r)>>1; if(L<=m) ans+=query(L,R,lson); if(R>m) ans+=query(L,R,rson); return ans; } void debug(int l,int r,int rt) { if(l==r){ printf("debug:%d %d ",l,sum[rt]); return; } int m=(l+r)>>1; debug(lson); debug(rson); } int main() { // freopen("in.txt","r",stdin); while(scanf("%d%d",&n,&q)!=EOF) { for(int i=1;i<=n;i++){ scanf("%lld",&b[i]); } build(1,n,1); for(int j=1;j<=q;j++){ scanf("%s%d%d",s,&l,&r); if(s[0]=='a'){ update(l,r,1,1,n,1); // debug(1,n,1); } else { printf("%lld ",query(l,r,1,n,1)); } } } return 0; }
对于上面存在的无效的操作,还有一种办法就是,线段树维护最小b[i]值,每当对区间的a[i]值进行+1处理时,我们就把相应区间的b[i]值进行减1,直到存在b[i]值为0,说明这时某些节点a[i]/b[i]值==1,于是我们把这些节点的sum数组+1,同时赋b[rt]重新为初始的b[i]值。可以看下两次所跑的时间,第二次跑的时间明显少了很多。
代码:

#include <cmath> #include <vector> #include <stdio.h> #include <iostream> using namespace std; #define ll long long #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int maxn=1e5+100; int n,q,l,r,c; char s[10]; ll b[maxn]; ll sum[maxn<<2],add[maxn<<2],minval[maxn<<2]; void PushUp(int rt) { sum[rt]=sum[rt<<1]+sum[rt<<1|1]; minval[rt]=min(minval[rt<<1],minval[rt<<1|1]); } void PushDown(int rt) { if(add[rt]){ add[rt<<1]+=add[rt]; add[rt<<1|1]+=add[rt]; minval[rt<<1]-=add[rt]; minval[rt<<1|1]-=add[rt]; add[rt]=0; } } void build(int l,int r,int rt) { add[rt]=0; if(l==r){ sum[rt]=0; minval[rt]=b[l]; return; } int m=(l+r)>>1; build(lson); build(rson); PushUp(rt); } void update(int L,int R,int c,int l,int r,int rt) { if(L<=l&&r<=R){ minval[rt]-=c; if(minval[rt]>0){ add[rt]+=c; return; } if(l==r&&minval[rt]<=0){ sum[rt]++; minval[rt]+=b[l]; return; } } PushDown(rt); int m=(l+r)>>1; if(L<=m) update(L,R,c,lson); if(R>m) update(L,R,c,rson); PushUp(rt); } ll query(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R){ return sum[rt]; } PushDown(rt); ll ans=0; int m=(l+r)>>1; if(L<=m) ans+=query(L,R,lson); if(R>m) ans+=query(L,R,rson); return ans; } void debug(int l,int r,int rt) { if(l==r){ printf("debug:%d %d ",l,sum[rt]); return; } int m=(l+r)>>1; debug(lson); debug(rson); } int main() { // freopen("in.txt","r",stdin); while(scanf("%d%d",&n,&q)!=EOF) { for(int i=1;i<=n;i++){ scanf("%lld",&b[i]); } build(1,n,1); for(int j=1;j<=q;j++){ scanf("%s%d%d",s,&l,&r); if(s[0]=='a'){ update(l,r,1,1,n,1); // debug(1,n,1); } else { printf("%lld ",query(l,r,1,n,1)); } } } return 0; }
Swaps and Inversions
题意:
给出一个序列,如果存在一对逆序对需要花费x元,现在可以对两个相邻的元素进行交换,每次交换需要花费y元,问最少要花费多少钱?
分析:
首先一次交换能够减少1对逆序对,那么num对逆序对,就需要num次交换。如果交换i次,花费cost=i*y+(num-i)*x=num*x-i*(y-x),不难看出,mincost=min(x,y)*num。
代码:

#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define ll long long #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int maxn=1e5+100; int sum[maxn<<2],x[maxn],Hash[maxn]; void PushUp(int rt) { sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt) { sum[rt]=0; if(l==r){ return; } int m=(l+r)>>1; build(lson); build(rson); } void update(int p,int add,int l,int r,int rt) { if(l==r){ sum[rt]+=add; return; } int m=(l+r)>>1; if(p<=m) update(p,add,lson); else update(p,add,rson); PushUp(rt); } int query(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R){ return sum[rt]; } int ans=0; int m=(l+r)>>1; if(L<=m) ans+=query(L,R,lson); if(R>m) ans+=query(L,R,rson); return ans; } int main() { // freopen("in.txt","r",stdin); int n,costx,costy; while(scanf("%d%d%d",&n,&costx,&costy)!=EOF) { ll cntx=0,cnty=0; build(0,n-1,1); for(int i=0;i<n;i++){ scanf("%d",&x[i]); Hash[i]=x[i]; } sort(Hash,Hash+n); int sz=unique(Hash,Hash+n)-Hash; for(int i=0;i<n;i++){ x[i]=lower_bound(Hash,Hash+sz,x[i])-Hash; } for(int i=0;i<n;i++){ cntx+=query(x[i]+1,n-1,0,n-1,1); update(x[i],1,0,n-1,1); } cnty=cntx; ll ans=min(cntx*costx,cnty*costy); printf("%lld ",ans); } return 0; }