2019 CCPC-Wannafly Winter Camp Day3(Div2, onsite)
solve 4/11
补题:5/11
Code:pai爷 zz
Thinking :pai爷
打表找规律,1张牌 10个不可能的 2张牌有 43 种不可能的 3张牌 有74 种不可能的 4 张牌有 5 种不可能的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cmath> #include<cstdlib> #include<cstdio> #include<algorithm> #include<string> #include<map> #include<queue> #include<vector> #include<stack> #define ll long long #define maxn 401000 int main() { int n,a[20]; while(scanf("%d",&n)!=EOF) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); if(n==6) printf("32\n"); else printf("891\n"); } }
分类加二分
留坑
生成树状压dp dp[ s ] [ i ] 表示s状态(节点组成)下,以i为根的树的信息。
留坑
Code:pai爷
Thinking:pai爷
莫比乌斯反演简单题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cmath> #include<cstdlib> #include<cstdio> #include<algorithm> #include<string> #include<map> #include<queue> #include<vector> #include<stack> #define ll long long #define maxn 401000 const int P=998244353; using namespace std; inline int rd() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int tot; int a,b,c,d,k,n,l; int sum[10000005],mu[10000005],pri[10000005],X[10000005],Y[10000005]; bool mark[10000005]; void getmu() { mu[1]=1; for(int i=2;i<=10000000;i++) { if(!mark[i]){mu[i]=-1;pri[++tot]=i;} for(int j=1;j<=tot&&i*pri[j]<=10000000;j++) { mark[i*pri[j]]=1; if(i%pri[j]==0) { mu[i*pri[j]]=0;break; } else mu[i*pri[j]]=-mu[i]; } } for(int i=1;i<=10000000;i++) sum[i]=sum[i-1]+mu[i]; } int cal(int n,int m) { if(n>m) swap(n,m); int ans=0,pos; for(int i=1;i<=n;i=pos+1) { pos=min(n/(n/i),m/(m/i)); ans=(ans+1ll*(sum[pos]-sum[i-1])*(n/i)*(m/i))%P; } return ans; } int main() { getmu(); scanf("%d",&n); for(int i=1;i<=n;i++) if(mu[i]!=0) { X[++l]=i; Y[l]=mu[i]; } ll su=0; for(int i=1;i<=l;i++) { a=1;b=n;c=1;d=n;k=X[i]; a--;c--; a/=k;b/=k;c/=k;d/=k; ll ans=cal(b,d); if(Y[i]==1) su=(su+ans+P)% P; else su=(su-ans+P)%P; } printf("%lld\n",su); }
G 排列
Code:zz
Thinking:zz
原序列p的前缀最小值数组肯定是非递增的,如果原序列p是递减的,那么q是递减的,否则,如果出现“1,2,3”这种排列,它的前缀最小数组是“1,1,1”,后一位是小于等于前一位的,q是“1,2,3”,这样q的这种部分就会出现递增的情况。因此,从前往后遍历整个q数组,如果递减,那么对应位置的p排列的地方就应该尽量小;如果遇到递增的地方,那么那片地方的数对应的p排列的地方应该尽量大,并且为了字典序最小,p排列的那片地方应该递增。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//#pragma comment(linker, "/STACK:102400000,102400000") #include<iostream> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<string> #include<math.h> #include<cmath> #include<time.h> #include<map> #include<set> #include<vector> #include<queue> #include<algorithm> #include<numeric> #include<stack> #include<bitset> #include<unordered_map> const int maxn = 0x3f3f3f3f; const double EI = 2.71828182845904523536028747135266249775724709369995957496696762772407663035354594571382178525166427; const double PI = 3.141592653589793238462643383279; using namespace std; int c[100010],ans[100010]; int main(void) { //ios::sync_with_stdio(false); int n,i,z1,z2,pos1,pos2,j; while(~scanf("%d",&n)) { for(i = 1;i <= n;i++) { scanf("%d",c + i); } z1 = 1; z2 = n; for(i = 1;i <= n;) { if(c[i] == z2) { //printf(" %d %d %d\n",i,c[i],z1); ans[c[i]] = z1++; //printf("%d %d %d\n",ans[c[i]],c[i],ans[5]); i++; } else { ans[c[i]] = z1++; i++; pos1 = i; for(;i <= n;i++) { if(c[i] == c[i - 1] + 1) { ans[c[i]] = z2--; } else { break; } } pos2 = i - 1; // printf("* %d %d\n",pos1,pos2); for(j = pos1;pos1 < pos2 && j <= pos1 + (pos2 - pos1) / 2;j++) { // printf("1111 %d %d %d %d %d %d\n",c[j],c[pos2 - j + pos1],ans[c[j]],c[pos2 - j + pos1],j,pos2 - j + pos1); int tmp = ans[c[j]]; ans[c[j]] = ans[c[pos2 - j + pos1]]; ans[c[pos2 - j + pos1]] = tmp; // printf("2222 %d %d %d %d\n",c[j],c[pos2 - j + pos1],ans[c[j]],c[pos2 - j + pos1]); } } } //printf(" %d\n",ans[5]); for(i = 1;i <= n;i++) { printf("%d",ans[i]); if(i != n) { printf(" "); } } printf("\n"); } return 0; }
Code:pai爷
Thinking:pai爷
差分约束+逆元
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cmath> #include<cstdlib> #include<cstdio> #include<algorithm> #include<string> #include<map> #include<queue> #include<vector> #include<stack> #define ll long long #define maxn 401000 const int p=998244353; int f[1010][1010],ans[1010][1010],z[1010][1010]; int n,m,q,l,r,a,b,c,d,an=0; ll qpow(ll a,ll b,ll p) { ll ret=1;a%=p; while(b) { if(b&1) ret=ret*a%p; b/=2;a=a*a%p; } return ret; } void init() { for(int i=1;i<=1000;i++) { f[i][0]=i*(i+1)/2; for(int j=1;j<=i;j++) f[i][j]=(i-j+1)*j; } } int main() { init(); scanf("%d%d%d",&n,&m,&q); for(int i=1;i<=n;i++) { scanf("%d%d",&l,&r); for(int j=l;j<=r;j++) { int sp=r-l+1; ans[i][j]=1ll*f[sp][j-l+1]*qpow(f[sp][0],p-2,p)%p; } } for(int i=1;i<=q;i++) { scanf("%d%d%d%d",&a,&b,&c,&d); for(int j=a;j<=c;j++) { z[j][b]++; z[j][d+1]--; } } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) z[i][j]+=z[i][j-1]; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(z[i][j]==0) { an=(an+ans[i][j])%p; } printf("%d\n",an); }
带权并查集 (树好像也可以做?)
补题:kk
按照题意描述,所有y挑战x的关系最后会形成一棵树的结构,n个人的总方案数是 3n 种,假设一个人被挑战(主场作战)a次,挑战别人(客场)b次,那么这个人存活到最后的方案数就是3n*(2/3)a*(1/3)b
也就是我们知道这个a和b就可以得到答案了,那要怎么维护呢。
这里用到并查集(jls niub!)
我们用w表示一个节点总共比赛的场次数,v表示主场作战的场次数,如果我们现在把y这个集合并向x这个集合(y挑战x),那么对于XW和Xv肯定都加一,而Yw也加一,如果我们接下来能很好的合并这些信息,那我们就AC了。
这里想了很久,才想明白要怎么做。我们先考虑暴力一点的并查集,就是不路径压缩,那每个节点就可以向上把所有父节点的信息全部加起来,就是我们最后要的某一个节点的W和V了,但是这样做会TLE,因为我们没有路径压缩,查找的时间复杂度很可能退化成O(n),但是我们又不能路径压缩(为什么不行,大家可以尝试一下,反正我自闭了一下午加一晚上)。
普通的带权并查集我们用的都是路径压缩版本的,而这里我们要按秩合并,这样查找的时间复杂度就可以被优化到O(logn)。
曾经我一直以为带权并查集的路径压缩和按秩合并是同一个东西,这道题真的学到了。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define CLR(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=200010; int fa[maxn],Rank[maxn]; ll w[maxn],v[maxn]; int n,m; int op,x,y; ll p= 998244353; struct node{ int fx; ll w,v; }; ll qpow(ll a,ll b){ a%=p; ll res=1; while(b>0) { if(b&1){ res*=a; res%=p; } b>>=1; a=a*a%p; } return res; } void init(){ for(int i=1;i<=n;i++){ fa[i]=i; w[i]=0; v[i]=0; Rank[i]=0; } } node find(int x){ if(x==fa[x]) return {fa[x],w[x],v[x]}; int tep=fa[x]; node e; e.w=w[x],e.v=v[x]; while(tep!=fa[tep]){ e.w+=w[tep],e.v+=v[tep]; tep=fa[tep]; } e.fx=tep; e.v+=v[tep],e.w+=w[tep]; return e; } void baba(int x,int y){ node ex=find(x),ey=find(y); if(ex.fx!=ey.fx){ w[ex.fx]+=1; v[ex.fx]+=1; w[ey.fx]+=1; v[ey.fx]+=0; if(Rank[ex.fx]>=Rank[ey.fx]) { w[ey.fx]-=w[ex.fx]; v[ey.fx]-=v[ex.fx]; fa[ey.fx]=ex.fx; Rank[ex.fx]++; }else{ w[ex.fx]-=w[ey.fx]; v[ex.fx]-=v[ey.fx]; fa[ex.fx]=ey.fx; Rank[ey.fx]++; } } } int main(){ while(cin>>n>>m) { init(); ll res=qpow(3,n); ll ans; while(m--) { scanf("%d%d",&op,&x); if(op==1){ scanf("%d",&y); baba(x,y); }else{ node ex=find(x); ll a=ex.v; ll b=ex.w-ex.v; ans=res*qpow(qpow(3,b),p-2)%p*qpow(2,a)%p*qpow(qpow(3, a),p-2)%p; printf("%lld\n",ans); } } } }
训练总结:
kk:今日贼菜,0贡献,键盘都没摸一下,石头剪刀布想到用带权并查集表示关系,但不会算情况数是多少,然后就换想法了?自己会的东西还是太少了,pai爷牛逼,zz牛逼!
pai爷:今天24点题目读错背锅了,之后知道是这样的就打表找不可能的情况,花了很多时间。整个人状态不是很好,做题缺少了节奏。最后一小时没有什么想法。
zz:今天做了一道签到题以后就开始跟着金老师研究24点,但是除了手写打表没什么贡献,后来又想了石头剪刀布和几何题,都没什么想法,最后也没做出来。