test
8.29
elimate 210
real 110
(1)merge—区间dp
爆零。。。整个想错
想成了贪心——就是每次都是小集合向大集合合并。。。
然后愉快爆零
反例:
7
2 2 2 3 1 7 7
std合并:{2,3} -1- {{2,3},1} -2- {{2,3,1},7} -3- {2,3,1,7},2,7} -1- {{2,3,1,7}{2,7}} -8- {{2,3,1,7,2,7},2} -4-
正解就是区间DP——一开始我扔了的那个
其实so easy预处理一下 [L,R]的元素个数就好
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=605;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
int n,a[N],st[N],top,siz[N][N],f[N][N];
int vis[N];
int main() {
n=read();
for(int i=1;i<=n;i++) a[i]=a[i+n]=read();
n<<=1;
for(int l=1;l<=n;l++)
for(int r=l;r<=n;r++) {
int sum=0;
for(int i=l;i<=r;i++)
if(++vis[a[i]]==1)
sum++,st[++top]=a[i];
siz[l][r]=sum;
while(top) vis[st[top--]]=0;
}
for(int len=2;len<=n;len++)
for(int l=1;l+len-1<=n;l++) {
int r=len+l-1;
for(int k=l+1;k<=r;k++)
f[l][r]=max(f[l][r],f[l][k-1]+f[k][r]+siz[l][k-1]*siz[k][r]);
}
n>>=1;
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,f[i][i+n-1]);
printf("%d\n",ans);
return 0;
}
(2)climb——贪心
维护两个大根堆,一个存 \(a-b\) ,一个存 \(a\)
每天枚举如果 \(now+a>=L\)直接出去
否则\(now+=a-b\(max\)\)
#include <queue>
#include <cstdio>
#include <utility>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e5+10;
typedef long long LL;
inline LL read() {
LL x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
int n;
LL m,a,b,c[N];
priority_queue< pair<LL,int> >q,Q;
bool vis[N];
int main() {
freopen("climb.in","r",stdin);
freopen("climb.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++) {
a=read(),b=read();
q.push(make_pair(a,i));
Q.push(make_pair(a-b,i));
}
for(int i=1;i<=n;i++) c[i]=read();
LL now=0,water=0;
for(int i=1;i<=n;i++) {
water+=c[i];
while(vis[q.top().second]) q.pop();
if(now+q.top().first>=m) {
printf("%d\n",i);
return 0;
} else {
now+=Q.top().first;
vis[Q.top().second]=1;
Q.pop();
if(now<=water) {
puts("-1");
return 0;
}
}
}
return 0;
}
8.31
elimate
200
real
125
啊本来真的200的啊啊啊
一定要先sort后unique!!!————T3
(1)序列——树状数组
我的做法:
先把序列扫一遍,然后把 \(s[i]-i < 0\) 的绝对值值存进树状数组(因为别的数随着我们枚举起点右移会变大,而这些会变小,但当其变为0之后又和前面的数一样了,所以树状数组维护这些数变小会影响的范围)注意区间的第一个我们暂且不管,然后随着枚举起点移动,答案减去上次第一个数的影响,加上这次最后新的数的影响,同时把最后这个数加入树状数组。
#include <cstdio>
#include <iostream>
using namespace std;
const int N=4e6+10;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
typedef long long LL;
int n,s[N];
LL ans,cnt,last;
LL c[N];
void upd(int x) {
for(int i=x;i<=2*n;i+=i&(-i))
c[i]++;
}
LL query(int x) {
LL ans=0;
for(int i=x;i;i-=i&(-i))
ans+=c[i];
return ans;
}
inline int jue(int x) {
return x>0?x:(-x);
}
int main() {
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
n=read();
for(int i=1;i<=n;i++) {
s[i]=s[i+n]=read();
last+=jue(s[i]-i);
if(s[i]<i&&i!=1)
upd(i-s[i]);
}
ans=last;
for(int i=2;i<=n;i++) {
cnt=query(2*n)-query(i-2);
last=last-jue(s[i-1]-1)+jue(s[i+n-1]-n)-cnt+(n-1-cnt);
if(s[i+n-1]<n) upd(jue(s[i+n-1]-n)-1+i);
ans=min(ans,last);
}
printf("%lld\n",ans);
return 0;
}
/*
3
2 3 1
--0
6
4 2 2 4 2 5
--6
*/
(2)迷宫——线段树
改编自cf413E
\(n\)很小,线段树维护区间 \([L,R]\) 内\(m\)行格子左边第\(x\)行到右边第\(y\)行的距离
合并两个区间时枚举中间点k进行合并即可
修改也就直接修改
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=200010;
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,m,q,g[6][N];
struct TREE{
int d[6][6];
void init(){ memset(d,0x3f,sizeof d); }
void get(int p) {
init();
for(int i=1;i<=n;i++)
if(g[i][p])
d[i][i]=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
d[i][j]=d[j][i]=min(d[i][j],d[i][j-1]+d[j][j]+1);
}
}t[N<<2];
TREE operator + (const TREE &a,const TREE &b) {
TREE c;c.init();
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++) {
Min(c.d[i][1],a.d[i][k]+b.d[k][1]+1);
Min(c.d[i][2],a.d[i][k]+b.d[k][2]+1);
Min(c.d[i][3],a.d[i][k]+b.d[k][3]+1);
Min(c.d[i][4],a.d[i][k]+b.d[k][4]+1);
Min(c.d[i][5],a.d[i][k]+b.d[k][5]+1);
}
return c;
}
void build(int l,int r,int p) {
if(l==r) {
t[p].get(l);
return;
}
build(l,mid,ls);
build(mid+1,r,rs);
t[p]=t[ls]+t[rs];
}
void modify(int l,int r,int pos,int a,int p) {
if(l==r) {
g[a][l]^=1;t[p].get(l);
return;
}
if(pos<=mid) modify(l,mid,pos,a,ls);
else modify(mid+1,r,pos,a,rs);
t[p]=t[ls]+t[rs];
}
TREE query(int l,int r,int L,int R,int p) {
if(L<=l&&r<=R) return t[p];
if(R<=mid) return query(l,mid,L,R,ls);
else if(L>mid) return query(mid+1,r,L,R,rs);
else return query(l,mid,L,R,ls)+query(mid+1,r,L,R,rs);
}
int main() {
n=read();m=read();q=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
g[i][j]=read();
build(1,m,1);
int op,x,y,xx,yy;
for(int i=1;i<=q;i++) {
op=read();x=read();y=read();
if(op==1) modify(1,m,y,x,1);
else {
xx=read();yy=read();
TREE ans=query(1,m,y,yy,1);
if(ans.d[x][xx]>=0x3f3f3f3f) puts("-1");
else printf("%d\n",ans.d[x][xx]);
}
}
return 0;
}
(3)动态数点——类似单调栈扩展
从小到大排个序(因为肯定小的数被整除的多一些)
枚举每个点,然后直接向左右扩展,
然后小优化 \(if(n-i+1<mx)\) \(break\);
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=500005;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n;
struct node{
int val,L;
bool operator < (const node &x) const {
return val>x.val;
}
}ans[N];
struct nodein{
int id,v;
bool operator < (const nodein &x) const{
return v<x.v;
}
}a[N];
inline int mo(int a,int b) {
return a-(a/b)*b;
}
int pos[N],b[N];
int main() {
// freopen("point.in","r",stdin);
// freopen("point.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)
a[i].id=i,a[i].v=b[i]=read();
sort(a+1,a+1+n);
int mx=0;
for(int i=1;i<=n;i++) {
if(n-i+1<mx) break;
int posl=a[i].id,posr=a[i].id,v=a[i].v,sum=0;
while(posl-1>=1&&mo(b[posl-1],v)==0) posl--,sum++;
while(posr+1<=n&&mo(b[posr+1],v)==0) posr++,sum++;
ans[i].L=posl,ans[i].val=sum;
mx=max(mx,sum);
}
sort(ans+1,ans+1+n);
int Ans=ans[1].val,cnt=0;
for(int i=1;i<=n;i++)
if(ans[i].val==Ans) pos[++cnt]=ans[i].L;
else break;
sort(pos+1,pos+1+cnt);
cnt=unique(pos+1,pos+1+cnt)-pos-1;
printf("%d %d\n",cnt,Ans);
for(int i=1;i<cnt;i++)
printf("%d ",pos[i]);
printf("%d",pos[cnt]) ;
return 0;
}
/*
5
4 6 9 3 6
30
15 15 3 30 9 30 27 11 5 15 20 10 25 20 30 15 30 15 25 5 10 20 7 7 16 2 7 7 28 7
*/
(3)Coin——染色计数
其实这题已经想出来了一大半——对博弈论的恐惧
奇偶性影响
如果有空行/列,那么也要对答案有影响:
如果你取之后再一步结束,那么你可以取一个空行,若无其他空行(奇偶性判断),那么你最后会扭转取胜
正解\(sg\)函数???
棋盘问题——二分图染色
反:r[i]+h[i]≡ 1(mod 2)
正:r[i]+h[i]≡ 0 mod 2)(想到这里了)
拆点——2-sat连边
2-sat连边后发现有向图可以看成无向图,然后tarjan缩点简化为并查集维护(多个\(log\))
根据2-sat的性质,这些连通块具有对称性
然后统计一对连通块中,奇偶性相同还是不同
相同的归为一大类(只用知道个数,对胜负没影响)
不同的也要统计个数——
奇数个:先手必胜
偶数个:先选没用的来扭转战局(到这里解决了前面的问题)
#include <cstdio>
#include <cstring>
#include <iostream>
#define N 1010
using namespace std;
int T,n,m,ans,cnt1,cnt2;
int c[N];
char s[N];
bool flag;
int hd[N],nxt[N],to[N],w[N],tot;
inline void add(int x,int y,int z) {
to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
void dfs(int x,int col){
if(!flag) return;
c[x]=col,cnt1+=(col==2),cnt2+=(col==3);
for(int i=hd[x];i;i=nxt[i]){
int y=to[i],z=w[i];
if(c[y]&&((z&&c[x]==c[y])||(!z&&c[x]!=c[y]))){
flag=false;
return;
}
if(!c[y]) dfs(y,col^z);
}
}
void clear(){
ans=tot=0,flag=true;
memset(c,0,sizeof(c));
memset(hd,0,sizeof(hd));
}
int main(){
scanf("%d",&T);
while(T--){
clear();
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%s",s+1);
for(int j=1;j<=m;++j){
if(s[j]=='o') add(i,j+n,0),add(j+n,i,0);
if(s[j]=='x') add(i,j+n,1),add(j+n,i,1);
}
}
for(int i=1;i<=n+m;++i){
if(c[i]) continue;
cnt1=cnt2=0,dfs(i,2);
if(!flag) break;
if((cnt1&1)==(cnt2&1)) ans^=cnt1&1;//奇偶性相同
else ans^=2;//奇数个就必胜,偶数个对ans抵消影响,取决于上面奇偶性相同的
}
if(flag) puts(ans?"3":"2");
else puts((n+m)&1?"1":"0");
}
return 0;
}
并查集写法
#include <iostream>
#include <cassert>
#include <cstring>
#include <cstdio>
#include <cctype>
using namespace std;
const int MAXN=405;
int fa[MAXN],siz[MAXN],vis[MAXN];
inline int read()
{
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
inline int readin()
{
char ch=getchar();
while(!isalpha(ch))
ch=getchar();
if(ch=='e') return 0;
if(ch=='x') return 1;
return 2;
}
inline int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline void merge(int x,int y)
{
int xx=find(x),yy=find(y);
if(xx==yy) return;
siz[xx]+=siz[yy],fa[yy]=xx;
}
int main()
{
int t=read();
while(t--)
{
memset(vis,0,sizeof(vis));
int flag=0,sum=0;
int n=read(),m=read();
for(int i=1;i<=n+m;i++)
fa[i]=i,siz[i]=1,fa[i+n+m]=i+n+m,siz[i+n+m]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int opt=readin();
if(opt==1)
{
merge(i,j+n+m+n);
merge(j+n,i+n+m);
}
else if(opt==2)
{
merge(i,j+n);
merge(i+n+m,j+n+m+n);
}
}
for(int i=1;i<=n+m;i++)
if(find(i)==find(i+n+m))
flag=1;//判断无解情况
if(flag) cout<<((n+m)&1)<<endl;
else
{
int sum1=0,sum2=0;
for(int i=1;i<=n+m;i++)
if(find(i)==i&&!vis[i])
{
int cur=find(i+n+m);
vis[i]=1,vis[cur]=1;
sum+=siz[i]+siz[cur];
if((siz[i]&1)^(siz[cur]&1))
sum2++;
else sum1^=(siz[i]&1);
}
clog<<n+m<<' '<<sum<<endl;
assert(sum==n+m);
if(sum2&1) cout<<3<<endl;
else cout<<2+(sum1&1)<<endl;
}
}
}
9.3
T1自认为想了个正解10min搞完并顺利过了大样例,然鹅只有55,不知所错
T2没有细想,写了个暴力就跑去看T3——真的不应该,T2是个大水题
T3想了至少1.5小时。。。惨遭爆零——不可做
(1)排列——gugugu
正解仿佛不可做
先gu这吧
下面是55分的
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int P=998244353;
const int N=100005;
typedef long long LL;
int n,k,x;
LL fac[N],inv[N];
LL qpow(LL a,LL b) {
LL ans=1;
while(b) {
if(b&1) ans=ans*a%P;
a=a*a%P;
b>>=1;
}
return ans;
}
LL C(int n,int m) {
return fac[n]*inv[n-m]%P*inv[m]%P;
}
LL ans;
int main() {
// freopen("permutation.in","r",stdin);
// freopen("permutation.out","w",stdout);
scanf("%d%d",&n,&k);
x=2*(n-k);
if(x>n) x=n;
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%P;
inv[n]=qpow(fac[n],P-2);
for(int i=n;i;i--) inv[i-1]=inv[i]*i%P;
ans=fac[n];
LL sum;
for(int i=1;i<=x;i++) {
sum=C(x,i)*fac[n-i]%P;
if(i&1) ans=(ans-sum+P)%P;
else ans=(ans+sum)%P;
}
printf("%lld\n",ans);
return 0;
}
/*
10!
3,628,800
4 3
---14
20 10
---496426549
*/
(2)苹果树——树上差分
我们发现如果一条树边有贡献则树上最多只有一条新加的边覆盖了它,那么我们只 要写一下 LCA(最近公共祖先算法)然后树上差分一下,被新边覆盖了恰好一次的树边 就会有 1 的贡献,而没有被覆盖的有 m 的贡献,直接统计一下就好了。
#include <queue>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=300005;
const int M=1000000;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n,m;
int hd[N],nxt[M],to[M],tot;
inline void add(int x,int y) {
to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
}
int fa[N],dep[N],siz[N],son[N];
void dfs_son(int x,int f) {
fa[x]=f;siz[x]=1;dep[x]=dep[f]+1;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==f) continue;
dfs_son(y,x);
siz[x]+=siz[y];
if(siz[y]>siz[son[x]]) son[x]=y;
}
}
int top[N];
void dfs_chain(int x,int tp) {
top[x]=tp;
if(son[x]) dfs_chain(son[x],tp);
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==son[x]||y==fa[x]) continue;
dfs_chain(y,y);
}
}
inline int LCA(int x,int y) {
while(top[x]!=top[y]) {
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
long long val[N],cnt0=-1,cnt1;//注意根那里会被统计,所以cnt0=-1
void get_ans(int x,int f) {
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==fa[x]) continue;
get_ans(y,x);
val[x]+=val[y];
}
if(val[x]==0) cnt0++;
if(val[x]==1) cnt1++;
}
int main() {
n=read();m=read();
int x,y;
for(int i=1;i<n;i++) {
x=read();y=read();
add(x,y),add(y,x);
}
dfs_son(1,0);dfs_chain(1,1);
for(int i=1;i<=m;i++) {
x=read();y=read();
val[x]++;val[y]++;
val[LCA(x,y)]-=2;
}
get_ans(1,0);
printf("%lld\n",cnt0*m+cnt1);
return 0;
}
(3)传送带——gugugu
9.4
elimate 150
real 70
(1)石头剪刀布——递归
爆成了20
好吧交换当时没想全,每次回溯都要暴力判断是否要交换左右儿子保证字典序最小
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int N=1e7;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n,fac[25],edge;
int sum[4],cnt[4];
int ans[N];
void dfs(int l,int r,int x) {
bool flag=0;
if(l==r) {
ans[l]=x;
sum[x]++;
return;
}
if(x==1)
dfs(l,mid,1),dfs(mid+1,r,2);
else if(x==2)
dfs(l,mid,2),dfs(mid+1,r,3);
else if(x==3)
dfs(l,mid,1),dfs(mid+1,r,3);
for(int i=l;i<=mid;i++) {
if(ans[i]<ans[mid+i-l+1]) break;
if(ans[i]>ans[mid+i-l+1]) {flag=1;break;}
}
if(flag)
for(int i=l;i<=mid;i++)
swap(ans[i],ans[mid+i-l+1]);
}
int main() {//1 布 2 石头 3 剪刀
cnt[2]=read();cnt[1]=read();cnt[3]=read();
n=cnt[1]+cnt[2]+cnt[3];
for(int i=1;i<=3;i++) {
sum[1]=sum[2]=sum[3]=0;
dfs(1,n,i);
if(sum[1]==cnt[1]&&sum[2]==cnt[2]&&sum[3]==cnt[3]) {
for(int j=1;j<=n;j++) {
if(ans[j]==1) printf("P");
if(ans[j]==2) printf("R");
if(ans[j]==3) printf("S");
}
return 0;
}
}
puts("IMPOSSIBLE");
return 0;
}
(2)投票——概率前缀后缀
概率这种真不大会。。。
数据看出3个点是\(n=k\),然后我推了一波规律
首先一个结论,对于这些人选择顺序不影响答案——打表也可证明
然后两个人一对,
然后对于小一点数我们可以暴力枚举子集,然后转化成n=k的问题
估计30--50实际30
第4,5个点由于精度问题,后面成了0...
30pts代码
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>
using namespace std;
int n,k;
double p[2005],pre[3],now[3],a[2005];
double ans;
void get() {
pre[1]=a[1]*a[2];
pre[0]=(1-a[1])*(1-a[2]);
pre[2]=a[1]+a[2]-2*a[1]*a[2];
for(int i=3;i<=k;i+=2) {
now[1]=a[i]*a[i+1];
now[0]=(1-a[i])*(1-a[i+1]);
now[2]=a[i]+a[i+1]-2*a[i]*a[i+1];
pre[2]=pre[2]*now[2]+pre[1]*now[0]+pre[0]*now[1];
pre[1]=pre[1]*now[1];
pre[0]=pre[0]*now[0];
}
ans=max(pre[2],ans);
}
int main() {
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%lf",&p[i]);
if(n==k) {
pre[1]=p[1]*p[2];
pre[0]=(1-p[1])*(1-p[2]);
pre[2]=p[1]+p[2]-2*p[1]*p[2];
for(int i=3;i<=n;i+=2) {
now[1]=p[i]*p[i+1];
now[0]=(1-p[i])*(1-p[i+1]);
now[2]=p[i]+p[i+1]-2*p[i]*p[i+1];
pre[2]=pre[2]*now[2]+pre[1]*now[0]+pre[0]*now[1];
pre[1]=pre[1]*now[1];
pre[0]=pre[0]*now[0];
cout<<pre[2]<<endl;
}
printf("%lf",pre[2]);
} else {
for(int s=1;s<(1<<n);s++) {
int cnt=0;
for(int j=0;j<n;j++)
if(s&(1<<j))
cnt++;
if(cnt==k) {
for(int j=0;j<n;j++)
if(s&(1<<j))
a[cnt--]=p[j+1];
get();
}
}
printf("%lf",ans);
}
return 0;
}
std做法:
我们可以感性理解,要想平票的概率大,肯定是选一些p[i]很大的,再选一些p[i]很小的;
将p[i] 从小到大排好序,则存在一个最优方案,其中选择的同学是一段前缀 和一段后缀。
这样我们就做一个前缀和后缀dp
#include <queue>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>
using namespace std;
int n,k;
double p[2005],h[2005][2005],t[2005][2005];
int main() {
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%lf",&p[i]);
sort(p+1,p+1+n);
h[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++)
if(j>0) h[i][j]=h[i-1][j-1]*p[i]+h[i-1][j]*(1-p[i]);
else h[i][j]=h[i-1][j]*(1-p[i]);
t[n+1][0]=1;
for(int i=n;i>=1;i--)
for(int j=0;j<=n-i+1;j++)
if(j>0) t[i][j]=t[i+1][j-1]*p[i]+t[i+1][j]*(1-p[i]);
else t[i][j]=t[i+1][j]*(1-p[i]);
double mx=0;
for(int i=1;i<=k;i++) {
double sum=0;
for(int j=0;j<=k/2;j++) //k/2即平票
sum+=h[i][j]*t[n-k+i+1][k/2-j];
mx=max(mx,sum);
}
printf("%lf\n",mx);
return 0;
}
(3)工厂——二分图+随机化gugugu
其实想出了80%正解
就是二分图模型,然后要么连成完全图,要么就一条边
关键没想出分成多个连通块——每个连通块连成完全图
无论哪种情况下 所有机器都能有人操作,就等价于,任意一个极大匹配都是完美匹配。
然后下面的就不会了。。。
http://192.168.14.142/submission/394
9.7
(1)古代龙人的谜题
能选出 多少个区间[L, R],满足——在它内部 选出一个中间点 mid,满足 L<mid<R,mid 是1,且区间[L, mid]和 [mid, R]中1的个数相等
my idea:暴力跳 ,复杂度O(n^2),期望70,由于玄学问题。。。成了58
正解:
合法的答案有两种形式: 000..000(一些0)mid(一些0)00...00 , 01001..(一些01混合)mid(一些01混合)01..并保证左右1的个数相同。
第一种比较好统计,我们先扫一遍记录1的位置\(pos[]\) ,然后对于每个1记录 \(l[]\) ,$r[] $表示左右各有多少个0,然后答案直接 \((l[i]-1)*(r[i]-1)\) ——注意小细节
第二种:我们可以只需要保证区间\([L, R]\)内有奇数个1即可,那么我们对于\(pos\)数组,分别按奇偶维护在L右边且区间\([1, R]\)内1的个数为奇数/偶数的点R的个数——形象来说就是跳,奇数的跳奇数 1,3,5 ...偶数的跳偶数2,4,6...(具体实现是代码里的\(sum[0/1]\)表示右边的合法的R的数累加)
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=2000010;
int n,m,id;
long long l[N],r[N],sum[3],ans;
int pre[N],nxt[N],a[N],pos[N];
char s[N];
int main() {
scanf("%d%d",&id,&n);
scanf("%s",s+1);
for(int i=1;i<=n;i++) a[i]=s[i]=='1';
int las=0;
for(int i=1;i<=n;i++) {
pre[i]=las;
if(a[i]) las=i,pos[++m]=i;
}
las=n+1;
for(int i=n;i;i--) {
nxt[i]=las;
if(a[i]) las=i;
}
for(int i=1;i<=m;i++) {
l[i]=pos[i]-pre[pos[i]];
r[i]=nxt[pos[i]]-pos[i];
}
//sum[0/1]即当前点右边1个数为奇数/偶数的合法R的个数
for(int i=m;i;i--) {
ans+=l[i]*sum[i&1];sum[i&1]+=r[i];
if(l[i]&&r[i]) ans+=(l[i]-1)*(r[i]-1);
}
printf("%lld\n",ans);
return 0;
}
(2)交错的字符串
见折半搜索
(3)世界第一的猛汉王
异色的点之间有一条已知的有向边,同色点之间是没有边的
\(covered[x]\)表示与x的距离小于等于D的异色点数目,\(covered[u, v]\)表示同时与 u和v距离都小于等于D的异色点数目(保证u, v同色)。则对于u, v是白色时我们有下式,u, v为黑色时同理,\(minans\)同理:
如上式,我们只需要求出 covered[]就可以了。由于曼哈顿距离不方便维护,我们可以将坐标轴斜转过来 (例如(x, y)变换为(x+y, x-y)),转换为切比雪夫距离(经过上例中的转换后 ,原条件变为两点切比雪夫距离不超过D),则求covered[]就变为询问一个矩形内点的个数, 这是数据结构经典问题,利用扫描线和线段树即可解决。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N=5000010;
const int M=10000010;
inline LL read() {
LL x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
LL n,m,dis,cntb,cntw;
struct node{
LL x,y,id,v;
node(){}
node(LL x_,LL y_,LL v_,LL id_):x(x_),y(y_),v(v_),id(id_){}
bool operator < (const node &a) const {
return x==a.x?id<a.id:x<a.x;
}
}b[N],w[N];
LL c[M],tot,s[M];
inline void upd(int x){for(int i=x;i<=tot;i+=i&(-i))c[i]++;}
inline LL Q(int x){LL ans=0;for(int i=x;i;i-=i&(-i))ans+=c[i];return ans;}
void lsh() {
for(int i=1;i<=n;i++) {
LL A=b[i].x-dis-1,B=b[i].y-dis-1,C=b[i].x+dis,D=b[i].y+dis;
w[++cntw]=node(C,D,1,i);
w[++cntw]=node(A,B,1,i);
w[++cntw]=node(C,B,-1,i);
w[++cntw]=node(A,D,-1,i);
s[++tot]=B;s[++tot]=D;
}
for(int i=1;i<=m;i++) {
LL A=w[i].x-dis-1,B=w[i].y-dis-1,C=w[i].x+dis,D=w[i].y+dis;
b[++cntb]=node(C,D,1,i);
b[++cntb]=node(A,B,1,i);
b[++cntb]=node(C,B,-1,i);
b[++cntb]=node(A,D,-1,i);
s[++tot]=B;s[++tot]=D;
}
sort(s+1,s+1+tot);
tot=unique(s+1,s+1+tot)-s-1;
for(int i=1;i<=cntb;i++)
b[i].y=lower_bound(s+1,s+1+tot,b[i].y)-s;
for(int i=1;i<=cntw;i++)
w[i].y=lower_bound(s+1,s+1+tot,w[i].y)-s;
sort(b+1,b+1+cntb);
sort(w+1,w+1+cntw);
}
LL ansb[N],answ[N];
int main() {
n=read();m=read();dis=read();
LL x,y;
for(int i=1;i<=n;i++) {
x=read();y=read();
s[++tot]=x-y;
b[++cntb]=node(x+y,x-y,0,0);
}
for(int i=1;i<=m;i++) {
x=read();y=read();
s[++tot]=x-y;
w[++cntw]=node(x+y,x-y,0,0);
}
lsh();
for(int i=1;i<=cntb;i++) {
if(!b[i].v) upd(b[i].y);
else answ[b[i].id]+=Q(b[i].y)*b[i].v;
}
//clear
for(int i=1;i<=tot;i++) c[i]=0;
for(int i=1;i<=cntw;i++) {
if(!w[i].v) upd(w[i].y);
else ansb[w[i].id]+=Q(w[i].y)*w[i].v;
}
sort(ansb+1,ansb+1+n);
sort(answ+1,answ+1+m);
LL mx=0,mn=0,val=0;
for(int i=1;i<=n;i++) {
mn+=ansb[i]*(n-i);
mx+=ansb[i]*(i-1);
val+=(ansb[i]-1)*ansb[i]/2;
}
for(int i=1;i<=m;i++) {
mn+=answ[i]*(m-i);
mx+=answ[i]*(i-1);
val+=(answ[i]-1)*answ[i]/2;
}
printf("%lld %lld\n",mn-val,mx-val);
return 0;
}
9.11
emilate 170
Real 170
(1)wzoi
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=1e6+10;
int n,ans;
char s[N];
int st[N],tp,a[N];
int main() {
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'0';
st[tp=1]=a[1];
for(int i=2;i<=n;i++) {
if(n-i<tp)
ans+=a[i]==st[tp--]?10:5;
else {
if(tp&&a[i]==st[tp]) ans+=10,tp--;
else st[++tp]=a[i];
}
}
printf("%d\n",ans);
return 0;
}
(2)course——随机gugugu
输出0都要30分
(3)painting
40分做法
暴力的dfs,每次从一个格子里最小的值扩展,向周围染色,周围未被染色过得染成w[now]+d
#include <queue>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=5005;
const int mod=1e9+7;
inline int read() {
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
typedef long long LL;
int n,m,k;
LL dis,d;
bool vis[N][N];
struct node{
int x,y;LL val;
bool operator < (const node &a) const {
return val>a.val;
}
node(){}
node(int x_,int y_,LL val_):x(x_),y(y_),val(val_){}
}p[505];
int jue(int x){ return x>0?x:(-x); }
LL jue2(LL x){ return x>0?x:(-x); }
priority_queue<node>Q;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
LL ans,cnt;
void dfs(node now) {
Q.pop();
for(int i=0;i<4;i++) {
int xx=dx[i]+now.x,yy=dy[i]+now.y;
if(xx<1||yy<1||xx>n||yy>m||vis[xx][yy]) continue;
cnt++;
vis[xx][yy]=1;
LL col=now.val+d;
(ans+=col)%=mod;
if(cnt==n*m) return;
Q.push(node(xx,yy,col));
}
if(!Q.empty()) dfs(Q.top());
}
int main() {
n=read();m=read();k=read();d=read();
for(int i=1;i<=k;i++) {
p[i].x=read();p[i].y=read();p[i].val=read();
vis[p[i].x][p[i].y]=1;
Q.push(p[i]);(ans+=p[i].val)%=mod;
}
cnt=k;
for(int i=1;i<=k;i++)
for(int j=1;j<=k;j++) {
dis=(jue(p[i].x-p[j].x)+jue(p[i].y-p[j].y))*d;
if(dis<jue2(p[i].val-p[j].val)) {
puts("IMPOSSIBLE");return 0;
}
}
dfs(Q.top());
printf("%lld\n",ans);
return 0;
}
/*
5 5 3 2
3 2 7
3 4 3
1 4 0
*/
9.12
elimate 80
read 70
(1)小学组——dfs
my: 状压$$2^nnm$$枚举,理论好像可过70,可能常数大了?
std:当时其实想到了就是我们只需要知道选哪几个数(可交换顺序),然后答案加上选的个数的阶乘;但是没想到的是可以把这n行m列个串压起来弄成n个二进制串,这样就把$$O(2n*n*m)$$降到了$$O(2n*n)$$,然后其实爆搜就是$$O(2^n)$$的
具体见代码
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int mod=1e9+9;
const int N=30;
typedef long long LL;
inline int read() {
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
int n,m;
LL ans;
char c[3];
LL fac[N],a[N],x;
void dfs(int pos,LL v,int num) {
if(pos==n+1) {
if(v==x) ans=(ans+fac[num])%mod;
return;
}
if(!num) dfs(pos+1,a[pos],1);
else {
if(c[0]=='&') dfs(pos+1,v&a[pos],num+1);
if(c[0]=='|') dfs(pos+1,v|a[pos],num+1);
if(c[0]=='^') dfs(pos+1,v^a[pos],num+1);
}
dfs(pos+1,v,num);
}
int main() {
fac[1]=1;
for(int i=2;i<=25;i++)
fac[i]=fac[i-1]*i%mod;
scanf("%s",c);
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(read()) a[i]|=(1<<j);
for(int i=1;i<=m;i++)
if(read()) x|=(1<<i);
dfs(1,0,0);
printf("%lld\n",ans);
return 0;
}