POI切题

orz hhw posted @ 2015年8月15日 10:58 in 做题记录 with tags POI 做题记录 , 2445 阅读

黈力骂我整天BZOJ的傻逼题切切没有希望了,于是我就来切切POI,现在切了几道

219

POI2000、2002、2012完 POI2004、2006、2007、2011、2015弃一坑 POI2008、2010、2014一 POI2013三 POI2003弃三坑

进度:1997:4/0/8;1998:3/0/9;1999:8/0/14;2000:11/0/12;2001:2/1/5;2002:12/0/12;2003:8/0/14;2004:12/0/13;2005:13/2/17;2006:16/1/17;2007:15/1/16;2008:14/1/16;2009:10/2/15;2010:15/3/18;2011:16/1/17;2012:17/0/17;2013:10/1/13;2014:15/1/17;2015:14/0/15;2016:4/0/5 总218/15/270

POI2010(15/18)(1753/1807)

T1.Guilds 并查集判断有没有孤立点即可

#include<cstdio>
#define N 200100
int n,m,i,x,y,p,q,fa[N],sz[N];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void uni(int x,int y){
	p=find(x);q=find(y);
	if(sz[p]>sz[q])p^=q^=p^=q;
	sz[q]+=sz[p];fa[p]=q;
}
int main(){
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)fa[i]=i,sz[i]=1;
	for(i=1;i<=m;i++)scanf("%d%d",&x,&y),uni(x,y);
	for(i=1;i<=n;i++)if(sz[i]==1&&fa[i]==i)return puts("NIE"),0;
	puts("TAK");
}

T2.Railway(未完成) 这题是双栈排序的加强版,不过我连双栈排序都没做过,就先把双栈排序做了

#include<cstdio>
#include<algorithm>
#define N 1111
#define M 2222222
using namespace std;
int n,i,j,tot,tp1,tp2,now,col[N],mi[N],a[N],fir[N],la[M],ne[M],st1[N],st2[N];bool flag;
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int co){
	col[x]=co;
	for(int i=fir[x];i;i=ne[i])if(!col[la[i]])dfs(la[i],3-co);else if(col[la[i]]==col[x])flag=1;
}
int main(){
	scanf("%d",&n);flag=0;
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(mi[n]=a[n],i=n-1;i>=1;i--)mi[i]=min(mi[i+1],a[i]);
	for(i=1;i<n;i++)for(j=i+1;j<n;j++)if(mi[j+1]<a[i]&&a[i]<a[j])ins(i,j),ins(j,i);
	for(i=1;i<=n;i++)if(!col[i])dfs(i,1);
	if(flag)return puts("0"),0;
	for(now=1,i=1;i<=n;i++){
		if(col[i]==1)st1[++tp1]=a[i],printf("a ");else st2[++tp2]=a[i],printf("c ");
		for(;(tp1&&st1[tp1]==now)||(tp2&&st2[tp2]==now);now++){
			if(tp1&&st1[tp1]==now)tp1--,printf("b");
			if(tp2&&st2[tp2]==now)tp2--,printf("d");
			if(now!=n)printf(" ");
		}
	}
}

可以考虑维护每个点的前向边和后向边,前向边还是比较好维护的,线段树搞搞就可以啦,后向边挺难写的,要线段树上挂链。。贴个标程算了

#include <cstdio>
#include <cstdlib>
#include <queue>
#include <vector>
#include <stack>
#include <iostream>
using namespace std;
const int MAXN=100057;
const int INF=1000000000;
int poc[MAXN],rev[MAXN],mini[MAXN],c[MAXN];
int n;
vector<int> tab[MAXN];
bool niet=false;
struct Drzewo {
	Drzewo* left;
    Drzewo* right;
	int key;
	int dist;
};

inline Drzewo* create(int x) {
	Drzewo* d = new Drzewo;
	d->key   = x;
	d->dist  = 1;
	d->left  = NULL;
	d->right = NULL;

	return d;
}

inline int top(Drzewo* d) {
	return (d==NULL)?(0):(d->key);
}

inline int dist(Drzewo* d){
	return (d==NULL)?(0):(d->dist);
}

Drzewo* merge(Drzewo* a, Drzewo* b) {
	if (a==NULL) return b;
	if (b==NULL) return a;
	// a!=NULL && b!=null
	if (top(b)<top(a)) swap(a,b);
	a->left = merge(a->left,b);
	if (dist(a->left) > dist(a->right) ) swap(a->right,a->left);
	if (a->right==NULL) a->dist = 0; else a->dist = 1+a->right->dist;
	return a;
}

Drzewo* deleteMin(Drzewo* d) {
	if (d!=NULL) {
		Drzewo* tmp = merge(d->left,d->right);
		free(d);
		return tmp;
	}
	return NULL;
}

inline bool isEmpty(Drzewo* d) {
	return (d==NULL);
}

void paint(int p,int colour) {
	c[p]=colour;
	for(int i=0;i<(int)tab[p].size();i++) {
		if (c[tab[p][i]]==0) paint(tab[p][i],colour%2+1); else
		if (c[tab[p][i]]!=(colour%2+1)) niet=true;
	}
}

bool checkOut() {
	stack<int> st[2];
	for(int i=0;i<2;i++) st[i].push(-1);
	int passed = 1;
	for(int i=1;i<=n;i++) {
		if (c[i]!=0) st[c[i]-1].push(poc[i]); else return false;
		bool change = true;
		while (change) {
			change = false;
			for(int j=0;j<2;j++) {
				while (st[j].top()==passed) {
					passed++;
					st[j].pop();
					change = true;
				}
			}
		}
	}
	return (passed==n+1);
}

int main() {
	Drzewo* stos[MAXN]; 
	int head = -1;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&poc[i]);
		rev[poc[i]]=i;
	}
	int prev=INF;
	for(int i=n;i>=1;i--) {
		mini[i]=prev;
		prev=min(prev,poc[i]);
	}
	for(int i=1;i<=n;i++) {
		int d=poc[i], e=mini[i];
		Drzewo* tmp = create(poc[i]);
		bool finished = false;
		while (head!=-1 && !finished) {
			Drzewo* v=stos[head];
			int val=top(v); 
			if (val<d) {
				tab[i].push_back(rev[val]);
				tab[rev[val]].push_back(i);
				tmp=merge(tmp,v);
				head--;
			} else finished = true;
		}
		finished = false;
		head++; stos[head]=tmp;
		while (head!=-1 && !finished) {
			tmp=stos[head]; head--;
			while (!isEmpty(tmp) && (top(tmp)<e)) tmp=deleteMin(tmp);
			
			if (!isEmpty(tmp)) {
				head++; stos[head] = tmp;
				finished = true;
			}
		}
	}
	for(int i=1;i<=n;i++) c[i]=0;
	for(int i=1;i<=n;i++) if (c[i]==0)  paint(i,1);
	niet = checkOut();
	if (!niet) printf("NIE\n"); else {
		printf("TAK\n");
		for(int i=1;i<=n;i++) printf("%d ",c[i]);
		printf("\n");
	}
}

T3 Beads 这种题目是常规字符串算法无法胜任的,要用HASH大法,两边各统计哈希值前缀和,然后求的时候从1到n暴力求,求的复杂度O(1),要求n/1+n/2+n/3...n/n,也就是nlgn次,然后再排序去重,复杂度nlg^2n

#include<cstdio>
#include<algorithm>
typedef unsigned long long LL;
#define S 999983LL
#define N 1111111
using namespace std;
int n,i,j,top,cnt,ans,tp,q[N];
LL w,a[N],s1[N],s2[N],st[N];
int main(){
	scanf("%d",&n);w=1;
	for(i=1;i<=n;i++)scanf("%llu",&a[i]),s1[i]=s1[i-1]*S+a[i];
	for(i=n;i;i--)s2[i]=s2[i+1]*S+a[i];
	for(i=1;i<=n;i++){
		top=0;w=w*S;
		for(j=1;j+i-1<=n;j+=i)st[++top]=min(s1[j+i-1]-s1[j-1]*w,s2[j]-s2[j+i]*w);
		sort(st+1,st+top+1);cnt=unique(st+1,st+top+1)-(st+1);
		if(cnt>ans)ans=cnt,q[tp=1]=i;else if(ans==cnt)q[++tp]=i;
	}
	printf("%d %d\n",ans,tp);
	for(i=1;i<=tp;i++)if(i==tp)printf("%d",q[i]);else printf("%d ",q[i]);
}

T4 Divine divisor 首先Miller-Rabin算法的基础是费马小定理,大概就是n是一个奇素数,1<=a<n,a^(n-1)mod n=1

可以用这个定理为基础进行Miller-Rabin判断一个大于等于3的数n是否为质数,然后进行T次如下操作:

随机选择一个整数a(2≤a≤n-2),计算y=a^r mod n,然后如果y^(n-1) mod n=1则不是负数,否则继续判断,若T次结束就是素数

这题大概就是弄出10^6以内的素数,然后剩下的数只可能是p,p^2或p·q,把p和p^2搞出来后互相gcd,然后剩下的要不然是素数要不然是不同的素数相乘,统计一下答案就可以了。。不过挺难弄的,不想搞了。。大概后面有几个点错了、、一开始是米勒罗宾写炸没快速乘。。改了也WA。。

然后改了一下写的姿势还是WA,和正确的程序对比下发现Millar-Rabin是错的。。看来4行的米勒罗宾是不靠谱的,就拉了个正常的米勒罗宾,然后就过了。。

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#define N 1111111
using namespace std;
typedef long long LL;
int i,j,n,w,now,res,ans,cnt,ww,prime[N],c[N];
LL g,z[N],W[]={0,2,3,11,67};bool f[N];
struct BI{
	int a[500],h;
	BI operator +(const BI &x)const{
    	BI ret;int p=0;
    	memset(ret.a,0,sizeof(ret.a));
    	for(int i=1;i<=h;i++){
    		ret.a[i]=a[i]+x.a[i]+p;
      		p=ret.a[i]/10;
      		ret.a[i]=ret.a[i]%10;
    	}
    	ret.h=h;if(p>0)ret.a[++ret.h]=p;
    	return ret;
  }
}ANS;
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
LL mul(LL a,LL b,LL M){
	LL ans=0;
	for(;b;b>>=1){
		if(b&1)ans=(ans+a)%M;
		a=(a+a)%M;
	}
	return ans;
}
LL pow(LL a,LL b,LL M){
	LL ans=1;
	for(;b;b>>=1){
		if(b&1)ans=mul(ans,a,M);
		a=mul(a,a,M);
	}
	return ans;
}
bool mr(LL n){
    if (n<2)return 0;if (n==2)return 1;if (!(n&1))return 0;
    LL d=n-1,r=0;
    while(!(d&1))r++,d>>=1;
    for (int a=1;a<=4;a++){
        LL now=W[a];if(now==n)continue;
        LL v=pow(now,d,n);if(v==1||v==n-1)continue;
        bool find=0;
        for(int b=1;b<r&&!find;b++){
            v=mul(v,v,n);
            if(v==n-1)find=1;
        }
        if(!find)return 0;
    }
    return 1;
}
void ql(){cnt++;for(int k=1;k<=n;k++)while(z[k]%g==0)z[k]/=g,c[cnt]++;}
int main(){
	for(w=1000000,i=2;i<=w;i++){
		if(!f[i])prime[++cnt]=i;
		for(j=1;j<=cnt&&i*prime[j]<=w;j++){
			f[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%lld",&z[i]);
		for(j=1;j<=cnt;j++)while(z[i]%(LL)prime[j]==0)c[j]++,z[i]/=(LL)prime[j];
	}
	for(i=1;i<n;i++)
		for(j=i+1;j<=n;j++)
			if(z[i]!=1&&z[j]!=1&&z[i]!=z[j]){
				g=gcd(z[i],z[j]);
				if(g!=1){
					ql();
					if(z[i]!=1)g=z[i],ql();
					if(z[j]!=1)g=z[j],ql();
				}
			}
	for(i=1;i<=n;i++)if(z[i]!=1){
		g=ceil((LL)sqrt(z[i]));
		if(g*g==z[i])ql();
	}
	for(i=1;i<=n;i++)if(mr(z[i]))g=z[i],ql(),ww++;
	for(i=1;i<=cnt;i++)if(c[i]>ans)ans=c[i],res=1;else if(c[i]==ans)res++;
	for(i=1;i<=n;i++)if(z[i]!=1){
		now=0;
		for(j=i;j<=n;j++)if(z[j]==z[i])now++;
		if(now>ans)ans=now,res=1;else if(now==ans)res+=2;
	}
	printf("%d\n",ans);
  	for(ANS.h=1,ANS.a[1]=1,i=0;i<res;i++)ANS=ANS+ANS;
	for(ANS.a[1]--,i=ANS.h;i;i--)printf("%d",ANS.a[i]);
}

T5 Intelligence test 写了个set结果T了,然后卡了卡常又T了,然后加了个读入优化才卡过

#include<cstdio>
#include<set>
using namespace std;
int T,n,i,x,m,pos;bool flag;char ch;
set<int> st[1000011];
void read(int &x){
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	for(x=0;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
}
int main(){
	read(n);
	for(i=1;i<=n;i++){
		read(x);
		st[x].insert(i);
	}
	read(T);
	while(T--){
		read(m);
		pos=0;flag=0;
		for(i=1;i<=m;i++){
			read(x);
			if(!flag)if(st[x].size()==0){flag=1;}
			else if(*--st[x].end()<=pos){flag=1;}
			else pos=*st[x].upper_bound(pos);
		}
		if(!flag)puts("TAK");else puts("NIE");
	}
}

T6 Antisymmetry 把Manacher的判断条件稍微改一下就可以了,顺便把Manacher背了一下,差不多背会了,不过交的时候没有注意奇数长度的是不合法的WA了一发

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500010
using namespace std;
typedef long long LL;
LL ans;int n,i,l,mx,p,r[N*2],ma[N*2];char s[N];
int main(){
	scanf("%d%s",&n,s);
	ma[l++]=7;ma[l++]=3;for(i=0;i<n;i++)ma[l++]=2*(s[i]-'0'+1),ma[l++]=3;
	for(i=1;i<l;i+=2){
		r[i]=mx>i?min(r[2*p-i],mx-i):1;
		for(;ma[i+r[i]]+ma[i-r[i]]==6;r[i]++);
		if(i+r[i]>mx)mx=i+r[i],p=i;
	}
	for(i=3;i<l;i+=2)ans+=(LL)(r[i]-1)/2;
	printf("%lld",ans);
}

T7 Hamsters 首先可以求出每两个串的最长后缀接前缀,这可以用HASH完成,这样dis[i][j]=len[j]-cal(i,j),注意自己接自己的时候不能cal(i,j)不能为len[j],然后可以通过类似Floyd+快速幂的方法快速求解,时间复杂度O(kn+n^3lgm),学会了动态开数组的新姿势

#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
typedef unsigned long long LL;
#define N 203
#define M 100100
#define S 999983LL
using namespace std;
int n,m,i,j,k,len[N];
LL miv,*h1[N],pow[M],f[N][N],g[N][N],ans[N][N];char s[M];
int cal(int x,int y){
	for(int i=min(len[x],len[y])-(len[y]<=len[x]);i;i--)if(h1[x][len[x]]-h1[x][len[x]-i]*pow[i]==h1[y][i])return i;
	return 0;
}
int main(){
	scanf("%d%d",&n,&m);miv=1e18;
	for(i=1;i<=n;i++){
		scanf("%s",s+1);len[i]=strlen(s+1);
		h1[i]=new LL[len[i]+1];h1[i][0]=0;
		for(j=1;j<=len[i];j++)h1[i][j]=h1[i][j-1]*S+(LL)(s[j]-'a'+1);
	}
	for(pow[0]=1,i=1;i<=100000;i++)pow[i]=pow[i-1]*S;
	for(i=1;i<=n;i++)for(j=1;j<=n;j++)f[i][j]=len[j]-cal(i,j);
	memset(ans,0x3f,sizeof ans);m--;
	for(i=1;i<=n;i++)ans[i][i]=0;
	for(;m;m>>=1){
		if(m&1){
			memset(g,0x3f,sizeof g);
			for(k=1;k<=n;k++)
				for(i=1;i<=n;i++)
					for(j=1;j<=n;j++)
						g[i][j]=min(g[i][j],ans[i][k]+f[k][j]);
			for(i=1;i<=n;i++)for(j=1;j<=n;j++)ans[i][j]=g[i][j];
		}
		memset(g,0x3f,sizeof g);
		for(k=1;k<=n;k++)
			for(i=1;i<=n;i++)
				for(j=1;j<=n;j++)
					g[i][j]=min(g[i][j],f[i][k]+f[k][j]);
		for(i=1;i<=n;i++)for(j=1;j<=n;j++)f[i][j]=g[i][j];
	}
	for(i=1;i<=n;i++)for(j=1;j<=n;j++)miv=min(miv,ans[i][j]+len[i]);
	printf("%llu",miv);
}

T8 Blocks 首先如果一个区间的平均值大于k即是合法的,对于每个k我们要求出这个最大的区间,可以用sum[i]=sum[i-1]+a[i]-k,这样维护一个单调下降的sum单调栈,就可以在O(n)的时间内求出答案

#include<cstdio>
#include<algorithm>
#define N 1111111
using namespace std;
int n,m,i,k,top,ans,q[N];
long long a[N],sum[N];
int main(){
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)scanf("%lld",&a[i]);
	while(m--){
		top=ans=0;scanf("%d",&k);
		for(i=1;i<=n;i++)sum[i]=sum[i-1]+a[i]-k;
		for(i=1;i<=n;i++)if(sum[q[top]]>sum[i])q[++top]=i;
		for(i=n;i;i--){
			for(;top&&sum[i]>=sum[q[top-1]];top--);
			ans=max(ans,i-q[top]);
		}
		!m?printf("%d",ans):printf("%d ",ans);
	}
}

T9 Sheep 首先如果一条对角线两边的羊都是偶数就是合法的,但如果暴力判断很明显会超时,可以以每个牧场边上的点为极点进行极角排序,然后就可以判断每条对角线是否合法了,接下来只要一个简单的区间DP就行啦,时间复杂度O(nmlgm+n^3)

#include<cstdio>
#include<algorithm>
#define N 620
#define M 20020
using namespace std;
int n,k,m,i,j,l,p,MO,f[N][N];bool v[N][N],ok;
struct EG{int x,y;}eg[M],q[N],st;
int multi(EG a,EG b,EG c){return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);}
bool cmp(EG a,EG b){return multi(st,a,b)<0;}
int main(){
	scanf("%d%d%d",&n,&m,&MO);
	for(i=1;i<=n;i++)scanf("%d%d",&q[i].x,&q[i].y);
	for(i=1;i<=m;i++)scanf("%d%d",&eg[i].x,&eg[i].y);
	for(i=1;i<=n;i++){
		st=q[i];sort(eg+1,eg+m+1,cmp);
		for(k=1,j=i%n+1,ok=1;k<=m;ok=!ok,k++)
			for(;i!=j&&(p=multi(st,q[j],eg[k]))<=0;j=j%n+1)if(p<0)v[i][j]=ok;
		for(;i!=j;j=j%n+1)v[i][j]=ok;
	}
	for(i=1;i<=n;i++)f[i][i%n+1]=1;
	for(l=2;l<=n;l++)
		for(i=1;i<=n;i++){
			j=(i+l-1)%n+1;
			for(k=1;k<=n;k++)if(v[i][k]&&v[k][j])f[i][j]=(f[i][j]+f[i][k]*f[k][j])%MO;
		}
	printf("%d",f[1][n]);
}

T10 Teleportation 首先很明显这是一个分层的图,每一层内互相连,然后层之间互相连,于是这个图最多只有四层

为了方便,先把和1相连的点放到第一层中,把和2相连的点放到第二层中,然后可以证明把剩下的点放到第一层或第二层不是好的选择,因为如果不放可以和他们的点全连,而且自己也可以连

剩下的点如果和第一层的点连过,就和所有第一层的点连,如果和第二层的点连过,就和所有第二层的点连,然后全部互相连,这样就能得到最优的答案啦

#include<cstdio>
#include<algorithm>
#define N 40040
#define M 1001000
using namespace std;
int n,m,i,a[M],b[M],f[N];bool f1[N],f2[N];long long ans,s1,s2,s3;
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=m;i++){
		scanf("%d%d",&a[i],&b[i]);
		if(a[i]<=2)f[b[i]]=a[i];
		if(b[i]<=2)f[a[i]]=b[i];
	}
	for(i=3;i<=n;i++)if(!f[i])s3++;else if(f[i]==1)s1++;else s2++;
	for(ans=(s3*(s3-1)+s1*(s1+1)+s2*(s2+1))/2,i=1;i<=m;i++){
		if(f[a[i]])if(f[a[i]]==1)f1[b[i]]=1;else f2[b[i]]=1;
		if(f[b[i]])if(f[b[i]]==1)f1[a[i]]=1;else f2[a[i]]=1;
	}
	for(i=3;i<=n;i++)if(!f[i])if(f1[i])ans+=s1;else if(f2[i])ans+=s2;else ans+=max(s1,s2);
	printf("%lld",ans-m);
}

T11 Monotonicity(双倍) 要用线段树维护DP,有点贪心的思想,不管符号的限制,每次尽量取最优的,不知道为何这样是对的。。

#include<cstdio>
#include<algorithm>
#define N 500100
using namespace std;
int n,k,w,i,ans,c,p[N],a[N],r[N],xd[N*2];
char s[3],ch;
struct XD{
	struct T{int l,r,mv;}t[N*8];
	void build(int k,int l,int r){
		t[k].l=l;t[k].r=r;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	}
	void ins(int k,int x,int z){
		int l=t[k].l,r=t[k].r,mid=(l+r)>>1;
		t[k].mv=max(t[k].mv,z);
		if(l==r)return;
		if(x<=mid)ins(k<<1,x,z);else ins(k<<1|1,x,z);
	}
	int find(int k,int x,int y){
		if(x>y)return 0;
		int l=t[k].l,r=t[k].r,mid=(l+r)>>1;
		if(x<=l&&r<=y)return t[k].mv;
		if(x>mid)return find(k<<1|1,x,y);
		else if(y<=mid)return find(k<<1,x,y);
		else return max(find(k<<1,x,mid),find(k<<1|1,mid+1,y));
	}
}dw,up;
void read(int &x){
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	for(x=0;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
}
int main(){
	read(n);read(k);w=1000000;
	for(i=1;i<=n;i++)read(a[i]);
	dw.build(1,1,w);up.build(1,1,w);
	for(i=0;i<k;i++){
		scanf("%s",s);
		if(s[0]=='<')p[i]=1;else if(s[0]=='>')p[i]=2;else p[i]=3;
	}
	for(i=1;i<=n;i++){
		r[i]=1+max(xd[a[i]],max(dw.find(1,1,a[i]-1),up.find(1,a[i]+1,w)));
		ans=max(r[i],ans);c=p[(r[i]-1)%k];
		if(c==1)dw.ins(1,a[i],r[i]);else if(c==2)up.ins(1,a[i],r[i]);else xd[a[i]]=r[i];
	}
	printf("%d",ans);
}

T12 The Minima Game dp[i]表示在i先手比后手多取的值,dp[i]=max(a[j+1]-dp[j]),单调维护a[j+1]-dp[j]的值即可

#include<cstdio>
#include<algorithm>
#define N 1111111
using namespace std;
int n,i;long long ma,a[N],dp[N];
int main(){
	scanf("%d",&n);for(i=1;i<=n;i++)scanf("%lld",&a[i]);sort(a+1,a+n+1);
	for(ma=a[1],i=1;i<=n;i++)dp[i]=ma,ma=max(ma,a[i+1]-dp[i]);
	printf("%lld",dp[n]);
}

T13 翻译都没有一定很难

T14 Frogs 由于第0-k近的点范围是单调增的,找第k近点就可以O(n)维护,然后就可以求出后一步走到的点,可这题如果直接倍增会MLE我也是醉了,然后压一下还是TLE也没办法。。然后必须用类似快速幂的方法做到O(n)的空间,没读入优化9.5S卡过

#include<cstdio>
#define N 1000100
typedef long long LL;int n,k,i,j,l,r,pos,ne[N][61];LL m,a[N];
int main(){
	scanf("%d%d%lld",&n,&k,&m);for(i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(l=1,r=k+1,ne[1][0]=k+1,i=2;i<=n;i++){
		for(;r<n&&a[r+1]-a[i]<a[i]-a[l];l++,r++);
		ne[i][0]=a[r]-a[i]>a[i]-a[l]?r:l;
	}
	for(j=1;j<=60;j++)for(i=1;i<=n;i++)ne[i][j]=ne[ne[i][j-1]][j-1];
	for(i=1;i<=n;i==n?printf("%d",pos):printf("%d ",pos),i++)for(pos=i,j=0;j<=60;j++)if(m&(1LL<<j))pos=ne[pos][j];
}
#include<cstdio>
#define N 1000100
typedef long long LL;int n,k,i,j,l,r,pos,ne[N],f[N],g[N],t[N];LL m,a[N];
int main(){
	scanf("%d%d%lld",&n,&k,&m);for(i=1;i<=n;i++)scanf("%lld",&a[i]),g[i]=i;
	for(l=1,r=f[1]=k+1,i=2;i<=n;i++){
		for(;r<n&&a[r+1]-a[i]<a[i]-a[l];l++,r++);
		f[i]=a[r]-a[i]>a[i]-a[l]?r:l;
	}
	for(;m;m>>=1){
		if(m&1){
			for(i=1;i<=n;i++)t[i]=f[g[i]];
			for(i=1;i<=n;i++)g[i]=t[i];
		}
		for(i=1;i<=n;i++)t[i]=f[f[i]];
		for(i=1;i<=n;i++)f[i]=t[i];
	}
	for(i=1;i<=n;i++)printf("%d%c",g[i],i==n?'\n':' ');	
}

T15 最短5K,蒟蒻无能为力

T16 Bridges 觉得这是一个欧拉回路判定问题,搜了下发现混合图欧拉回路判定需要网络流,首先所有点入度-出度必须是2的倍数,然后对于入不敷出的点连汇点/2,源点入度多的/2,然后对于每条无向边先设好方向,连边流量为1,就可以达到无向边效果,检查是否满流即可,这题再加个二分答案就行了,不过我WA了也不想调了

#include<cstdio>
#include<cstring>
#define CL(a) memset(a,0,sizeof(a))
#define inf 1e9
#define N 2222
#define M 8888
int n,m,i,u,s,t,l,r,mid,now,tot,tmp,flow,ext,ans,a[N],b[N],c[N],d[N],fir[N],dist[N],cur[N],pre[N],numb[N],in[N],va[M],ne[M],la[M];
void ins(int x,int y,int z){
	la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;
	la[++tot]=x;va[tot]=0;ne[tot]=fir[y];fir[y]=tot;
}
int ISAP(){
	CL(numb);CL(dist);CL(pre);
	for(i=1;i<=t;i++)cur[i]=fir[i];u=s;numb[0]=t;
	while(dist[s]<t){
		if(u==t){
			now=inf;
			for(i=s;i!=t;i=la[cur[i]])if(va[cur[i]]<now)now=va[cur[u=i]];
			for(i=s;i!=t;i=la[cur[i]]){
				va[cur[i]]-=now;
				va[cur[i]^1]+=now;
			}
			flow+=now;
		}
		for(i=cur[u];i;i=ne[i])if(va[i]&&dist[la[i]]+1==dist[u])break;
		if(i){
			cur[u]=i;
			pre[la[i]]=u;
			u=la[i];
		}else{
			if(0==--numb[dist[u]])break;
			for(i=cur[u]=fir[u],tmp=t;i;i=ne[i])
				if(dist[la[i]]<tmp&&va[i])tmp=dist[la[i]];
			++numb[dist[u]=tmp+1];
			if(u!=s)u=pre[u];
		}
	}
	return flow;
}
bool ok(int w){
	tot=1;CL(fir);CL(in);CL(va);CL(la);CL(ne);s=n+1;t=s+1;ext=0;
	for(i=1;i<=m;i++){
		if(c[i]<=w&&d[i]<=w)in[a[i]]++,in[b[i]]--,ins(a[i],b[i],1);
		else if(c[i]<=w)in[a[i]]--,in[b[i]]++;
		else if(d[i]<=w)in[b[i]]--,in[a[i]]++;
		else return 0;
	}
	for(i=1;i<=n;i++)if(in[i]&1){return 0;}else if(in[i]>0)ins(s,i,in[i]/2),ext+=in[i]/2;else if(in[i]<0)ins(i,t,(-in[i])/2);
	return ISAP()>=ext;
}
int main(){
	freopen("mos2b.in","r",stdin);freopen("mos.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
	ans=-1;l=0;r=1000;
	while(l<=r){
		mid=l+r>>1;
		if(ok(mid))ans=mid,r=mid-1;else l=mid+1;
	}
	if(ans==-1)puts("NIE");else printf("%d",ans);
}

T17 Pilots 维护两个单调队列即可

#include<cstdio>
#include<algorithm>
#define N 3001000
using namespace std;
int n,k,i,h1,t1,h2,t2,t,ans,a[N],q1[N],q2[N];
int main(){
	scanf("%d%d",&k,&n);for(i=1;i<=n;i++)scanf("%d",&a[i]);
	h1=1;h2=1;t1=0;t2=0;t=1;
	for(i=1;i<=n;i++){
		for(;h1<=t1&&a[i]>=a[q1[t1]];t1--);
		for(;h2<=t2&&a[i]<=a[q2[t2]];t2--);
		q1[++t1]=i;q2[++t2]=i;
		while(a[q1[h1]]-a[q2[h2]]>k)if(q1[h1]<q2[h2])t=q1[h1++]+1;else t=q2[h2++]+1;
		ans=max(ans,i-t+1);
	}
	printf("%d",ans);
}

感觉收获还是挺大的,切这一套题我学会了大素数判定,背会了Manacher,加深了对字符串HASH的理解,学会了使用set的正确姿势,还学会了一些倍增的黑科技,以及混合图欧拉回路的判定

POI2011(16/17)(1471/1544)

T1 Tree Rotations 写了个暴力归并然后T了,我只能想到Splay啊,可别人代码都这么短。。

还是乖乖地写了下Splay,代码也不长,不过是在搞不懂最短的不到1K是怎么做到的,写Splay这道题就比较直观了,只要暴力把小的合到大的上面然后统计答案就可以了

#include<cstdio>
#include<cstdio>
#include<algorithm>
#define N 222222
using namespace std;
typedef long long LL;
int n,x,top,a[N],b[N];LL ans;
void get(){
	int i,j,k,l,r,mid;LL now=0;
	scanf("%d",&x);
	if(!x){
		l=top+1;get();mid=top;get();r=top;
		for(k=l,j=mid+1,i=l;i<=mid;b[k++]=a[i],i++)
			for(;j<=r&&a[j]<a[i];b[k++]=a[j++])now+=(LL)(mid-i+1);
		for(;j<=r;b[k++]=a[j++]);
		for(k=l;k<=r;k++)a[k]=b[k];
		ans+=min(now,(LL)(mid-l+1)*(LL)(r-mid)-now);
	}else a[++top]=x;
}
int main(){
	scanf("%d",&n);get();
	printf("%lld",ans);
}
#include<cstdio>
#include<algorithm>
#define N 2222222
using namespace std;
typedef long long LL;
int n,x,y,tot,top,fa[N],sz[N],q[N],c[N][2],val[N];LL ans,now;
void ps(int x){sz[x]=sz[c[x][0]]+sz[c[x][1]]+1;}
void R(int x){
	int y=fa[x],k=(c[y][0]==x);
	c[y][!k]=c[x][k];fa[c[x][k]]=y;
	fa[x]=fa[y];c[fa[y]][c[fa[y]][1]==y]=x;
	c[x][k]=y;fa[y]=x;ps(y);
}
void sy(int x,int &rt){for(;y=fa[x];R(x))if(fa[y])R((x==c[y][0])==(y==c[fa[y]][0])?y:x);ps(x);rt=x;}
void Nw(int &x,int la,int key){x=++tot;fa[x]=la;val[x]=key;sz[x]=1;}
void ins(int key,int &rt){
	int x=rt;for(;c[x][key<val[x]];x=c[x][key<val[x]]);
	Nw(c[x][key<val[x]],x,key);sy(c[x][key<val[x]],rt);now+=sz[c[rt][0]];
}
void tr(int x){if(!x)return;q[++top]=val[x];tr(c[x][0]);tr(c[x][1]);}
int get(){
	scanf("%d",&x);
	if(!x){
		int l=get(),r=get();LL sum=(LL)sz[l]*(LL)sz[r];if(sz[l]<sz[r])swap(l,r);now=0;top=0;
		tr(r);sort(q+1,q+top+1);for(int i=1;i<=top;i++)ins(q[i],l);ans+=min(now,sum-now);return l;
	}else{val[++tot]=x;sz[tot]=1;return tot;}
}
int main(){scanf("%d",&n);get();printf("%lld",ans);}

T2 Difference 每次维护出cnt[i]-cnt[j]-(cnt[i2]-cnt[j2]),可以维护出cnt[i]-cnt[j]的最小值,就可以快速转移了

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,i,j,c,ans,f[27][27],g[27][27];char s[1001000];
int main(){
	scanf("%d%s",&n,s+1);
	for(i=1;i<=n;i++){
		c=s[i]-'a';
		for(j=0;j<26;j++){
			f[c][j]++;f[j][c]--;
			if(!g[j][c])g[j][c]=1;
			if(g[j][c]==2){g[j][c]--;f[j][c]++;}
			if(f[j][c]<=-1){f[j][c]=-1;g[j][c]=2;}
            if(g[j][c])ans=max(ans,f[j][c]);
            if(g[c][j])ans=max(ans,f[c][j]);
		}
	}
	printf("%d",ans);
}

T3 Shift 先把1~n-2暴力调整到相应位置,如果符合则直接输出,否则如果是奇数不合法,偶数把n不断往前跳得到正确答案,WA了好久,后来发现步数>n要取模,而且模为0还要删去。。

#include<cstdio>
#define N 2222
int n,i,u,x,y,t,tp,pt,a[N*N],b[N*N];
int abs(int x){return x>0?x:-x;}
void add(int x){x*b[tp]>0?b[tp]+=x:b[++tp]=x;}
void c1(){u=(u-1+n)%n;add(1);}
void c2(){
	x=(u+1)%n;y=(u+2)%n;t=a[u];
	a[u]=a[y];a[y]=a[x];a[x]=t;
	add(-1);
}
void c3(){u=(u+1)%n;add(n-1);}
int main(){
	for(scanf("%d",&n);i<n;i++)scanf("%d",&a[i]);
	for(i=2;i<n-1;i++){
		for(;a[u]!=i;c1());
		for(;a[(u-1+n)%n]!=i-1;){
			c1();
			if(a[(u-1+n)%n]!=i-1)c1(),c2();else c2(),c2();
		}
	}
	for(;a[u]!=1;c1());
	if(a[(u+n-1)%n]!=n){
		if(n&1)return puts("NIE DA SIE"),0;
		for(c1();a[(u+1)%n]!=n;)c2(),c2(),c3(),c3();
		for(;a[u]!=1;)c1();
	}
	for(i=1;i<=tp;i++)if(abs(b[i])%n!=0)a[++pt]=abs(b[i])%n,b[pt]=b[i]>0?1:0;
	for(printf("%d\n",pt),i=1;i<=pt;i++)printf("%d",a[i]),printf("%c ",b[i]?'a':'b');
}

T4 Conspiracy 先2-SAT构造一组方案,然后要调整的话肯定最多只能调整一对,可以求出每个调整影响到的人,如果>1则不合法,如果没有影响到人则把答案加上,影响到则把两个人一起改的答案加上,不过要注意一个块只有1个人的时候要特判

#include<cstdio>
#define N 5050
int n,m,i,j,t,x,ans,c0,c1,g[N][N],q[N<<1],a[N],b[N];bool v[N<<1];
int dfs(int x){
	if(v[x>n?x-n:x+n])return 0;
	if(v[x])return 1;v[q[++t]=x]=1;
	if(x>n){for(int i=1;i<=n;i++)if(i!=x-n&&g[x-n][i])if(!dfs(i))return 0;}
	else for(int i=1;i<=n;i++)if(i!=x&&!g[x][i])if(!dfs(i+n))return 0;
	return 1;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)for(scanf("%d",&m);m--;g[i][x]=1)scanf("%d",&x);
	for(i=1;i<=n;i++)if(!v[i]&&!v[i+n])
		if(t=0,!dfs(i)){
			for(;t;v[q[t--]]=0);
			if(!dfs(i+n))return puts("0"),0;
		}
	for(i=1;i<=n;i++)if(v[i])c0++;else c1++;
	for(i=1;i<=n;i++)if(v[i])for(j=1;j<=n;j++)if(!v[j]&&g[i][j])if(!a[i])a[i]=j;else a[i]=-1;
	for(i=1;i<=n;i++)if(!v[i])for(j=1;j<=n;j++)if(v[j]&&!g[i][j])if(!b[i])b[i]=j;else b[i]=-1;
	for(i=1;i<=n;i++)if(v[i])ans+=!a[i]&&c0>1;else ans+=!b[i]&&c1>1;
	for(i=1;i<=n;i++)if(v[i])for(j=1;j<=n;j++)if(!v[j]&&(!a[i]||a[i]==j)&&(!b[j]||b[j]==i))ans++;
	printf("%d",ans+(c0&&c1));
}

T5 Lightning Conductor 一开始化出了式子max(a[j]+sqrt(abs(i-j)))-a[i],但感觉无从下手,试了下斜率优化发现不可行,又回去想了一天多,还是没想出来,只好去看题解。这原来是一道不用斜率优化的1D1D单调性优化,和NOI2009的诗人小G差不多。。单调性DP优化几乎都忘光了。。差不多就是对于区间[l,r]都用值p来更新答案。。

对于这道题sqrt(x)-sqrt(x-1)单调递减,如果k<j<i且对于i而言j比k优,k就是无用的。。

维护很多个区间三元组{p,l,r},对于每个插入的i,他能管得区间一定是[x,n]

如果对于n,当前i比队尾更优,就把队尾弹出,最后的队尾(p,l,r)满足在l上p比i优,在r上i比p优,可以二分查找这个分界点x

然后将队尾变成(p,l,x-1),然后将(i,x+1,n)加入,这样就完成了单调性DP优化,这也差不多是单调性DP的基本步骤。。斜率优化如果想不出不妨想想单调性优化。。

#include<cstdio>
#include<cmath>
#include<algorithm>
#define N 500500
using namespace std;
double f[N],g[N];
int n,i,l,r,mid,a[N];
struct data{int p,l,r;}q[N];
double cal(int j,int i){return a[j]+sqrt(abs(i-j))-a[i];}
void dp(double *f){
	for(int h=1,t=0,i=1;i<=n;i++){
		q[h].l++;if(h<=t&&q[h].l>q[h].r)h++;
		if(h>t||cal(i,n)>cal(q[t].p,n)){
			for(;h<=t&&cal(i,q[t].l)>cal(q[t].p,q[t].l);t--);
			if(h>t)q[++t]=(data){i,i,n};else{
				l=q[t].l;r=q[t].r;
				while(l<r){
					mid=l+r>>1;
					if(cal(q[t].p,mid)>cal(i,mid))l=mid+1;else r=mid;
				}
				q[t].r=l-1;q[++t]=(data){i,l,n};
			}
		}
		f[i]=cal(q[h].p,i);
	}
}
int main(){
	scanf("%d",&n);for(i=1;i<=n;i++)scanf("%d",&a[i]);
	dp(f);reverse(a+1,a+n+1);dp(g);
	for(i=1;i<=n;i++)printf("%d\n",(int)ceil(max(f[i],g[n-i+1])));
}

T6 Lollipop 首先所有前缀和都是可行的,然后对于每个数找到他右边最多有几个2,然后对每个2进行如下判定,如果1位置后面的2数量比i后面的2少,就向右移动ex[1]位,否则就向右移ex[i]位,但如果超过n就是不合法的

#include<cstdio>
#define N 2001000
int n,m,i,sum,x,l[N],r[N],ex[N];char s[N];
int main(){
	scanf("%d%d%s",&n,&m,s+1);
	for(i=n;i;i--)s[i]=='W'?ex[i]=0:ex[i]=ex[i+1]+1;
	for(i=1;i<=n;i++){
		sum+=s[i]=='W'?1:2;
		l[sum]=1;r[sum]=i;
		if(s[i]=='T'){
			if(ex[1]<ex[i])l[sum-1]=ex[1]+2,r[sum-1]=i+ex[1];else
			if(i+ex[i]!=n+1)l[sum-1]=ex[i]+1,r[sum-1]=i+ex[i];
		}
	}
	while(m--){
		scanf("%d",&x);
		if(x>sum||!l[x])puts("NIE");else printf("%d %d\n",l[x],r[x]);
	}
}

T7 Temperature 一开始写个贪心WA了,后来发现贪心有点不对,要改成单调队列,维护单调上升的l[]值来得到答案

#include<cstdio>
#define N 1111111
int n,i,ans=1,h=1,t,la=1,now,l[N],r[N],q[N];char ch;
int main(){
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%d%d",&l[i],&r[i]);
		for(;l[q[t]]<=l[i]&&h<=t;t--);
		q[++t]=i;now=la;
		for(;l[q[h]]>r[i];)now=q[h++]+1;
		ans=ans>i-now+1?ans:i-now+1;la=now;
	}
	printf("%d",ans);
}

T8 Strongbox 很神的题,弄出所有gcd去重后判断。。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
LL m,ans,a[251000];int n,i,tot;
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
void check(LL x){
	for(int i=1;i<=tot;i++)if(a[i]%x==0)return;
	ans=min(ans,x);
}
int main(){
	scanf("%lld%d",&m,&n);ans=m;
	for(i=1;i<=n;i++)scanf("%lld",&a[i]),a[i]=gcd(a[i],m);
	sort(a+1,a+n);for(i=1;i<n;i++)if(a[i]!=a[i+1])a[++tot]=a[i];
	for(i=1;(LL)i*i<=a[n];i++)if(a[n]%i==0){
		check(i);check(a[n]/i);
	}
	printf("%lld",m/ans);
}

T9 Garbage 就是有一些需要改变的边,每次可以改变一个环的状态,求可行解

如果需要改的边的点的度数为奇数则无解,因为每次改变的是一个环

首先如果两个环相交,则多出来的是不必要的,所以每个环一定不相交,可以每次找出一个环然后删掉

于是可以对每个点暴力扩展找环,加一个对走边条件的小优化,时间复杂度就是O(n+m)的了

#include<cstdio>
#define M 2001000
int n,m,x,y,p,q,i,t,tot,cnt,scc,fir[M],st[M],pos[M],du[M],a[M],ne[M],la[M];bool is[M],v[M];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;du[x]++;}
int main(){
	for(scanf("%d%d",&n,&m),tot=1;m--;){scanf("%d%d%d%d",&x,&y,&p,&q);if(p^q)ins(x,y),ins(y,x);}
	for(i=1;i<=n;i++)if(du[i]&1)return puts("NIE"),0;
	for(x=1;x<=n;x++)for(st[t=1]=y=x;y;y=q){
		for(is[y]=1,q=0,i=fir[y];i;i=ne[i])if(!v[i]){
			v[i]=v[i^1]=1;fir[y]=ne[i];
			if(is[p=la[i]]){
				for(a[++cnt]=q=p;st[t]!=p;is[st[t--]]=0)a[++cnt]=st[t];
				pos[++scc]=cnt;
			}else q=st[++t]=p;
			break;
		}
		if(!q&&t)q=st[t--];
	}
	for(printf("%d\n",scc),x=1;x<=scc;printf("%d\n",a[pos[x-1]+1]),x++)for(printf("%d ",pos[x]-pos[x-1]),i=pos[x-1]+1;i<=pos[x];i++)printf("%d ",a[i]);
}

T10 Plot 可以二分每个覆盖圆半径的最大值然后进行判断,进行判断的时候当然不能在没有随机的情况下直接暴力判,要先倍增找到一个合适的范围,然后二分查找这个范围中该半径控制的范围,这样时间复杂度是n·log^2n?虽然我觉得是nlg^3n,而如果不倍增复杂度是n^2lgn的。。反正这题有5分钟的时限,去爆爆OJ的感觉挺好的

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#define eps 1e-9
#define N 100010
using namespace std;
int n,m,i,cnt,ans[N][2];
double l,r,mid,R;
struct P{double x,y;}a[N],b[N],O;
double dis(P x,P y){return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));}
P get(P a,P b,P c){
	double a1=b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2,a2=c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2,d=a1*b2-a2*b1;
	return (P){a.x+(c1*b2-c2*b1)/d,a.y+(a1*c2-a2*c1)/d};
}
void cal(int l,int r){
	int i,j,k,n=0;
	for(i=l;i<=r;i++)a[++n]=b[i];
	for(i=2;i<=n;i++)swap(a[1+(rand()%n)],a[i]);
	for(O=a[1],R=0,i=2;i<=n;i++)if(R+eps<dis(O,a[i]))
		for(O=a[i],R=0,j=1;j<i;j++)if(R+eps<dis(O,a[j]))
			for(O=(P){(a[i].x+a[j].x)/2,(a[i].y+a[j].y)/2},R=dis(O,a[i]),k=1;k<j;k++)
				if(R+eps<dis(O,a[k]))O=get(a[i],a[j],a[k]),R=dis(O,a[k]);
}
bool ok(double x){
	int i,j,l,r,mid,t,sum=0;
	for(i=1;i<=n;i=t+1){
		for(j=1;i+(1<<j)-1<=n;j++){
			cal(i,i+(1<<j)-1);
			if(R>x+eps)break;
		}
		t=i,l=i+(1<<(j-1))-1;r=i+(1<<j)-1;if(r>n)r=n;
		while(l<=r){
			cal(i,mid=(l+r)>>1);
			if(R<x+eps)l=(t=mid)+1;else r=mid-1;
		}
		if(++sum>m)return 0;
		ans[++cnt][0]=i;ans[cnt][1]=t;
	}
	return 1;
}
int main(){
	scanf("%d%d",&n,&m);for(i=1;i<=n;i++)scanf("%lf%lf",&b[i].x,&b[i].y);
	cal(1,n);r=R;
	if(m>1)for(;r-l>eps;cnt=0)if(ok(mid=(l+r)/2))r=mid;else l=mid;
	ok(r);printf("%.8lf\n%d\n",r,cnt);
	for(i=1;i<=cnt;i++)cal(ans[i][0],ans[i][1]),printf("%.8lf %.8lf\n",O.x,O.y);
}

T11 Dynamite 先二分爆破的时间,然后贪心,对于每个点,有三种情况,一种是子树中有选择的点还有对上面的贡献,一种是子树中还有未覆盖到的关键点,还有一种是既没有贡献点也没有关键点,贪心进行三种情况的转移,如果能利用贡献尽量利用贡献,不能了再选该点为关键点

#include<cstdio>
#include<algorithm>
#define N 300030
using namespace std;
int n,m,x,y,tot,cnt,ans,l,r,mid,i,a[N],fir[N],ne[N<<1],la[N<<1],f[N],st[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int fa,int lim){
	int i,y,n1=-1,n2=a[x]-1;
	for(i=fir[x];i;i=ne[i]){
		y=la[i];
		if(y!=fa){
			dfs(y,x,lim);
			if(st[y]==0)n1=max(n1,f[y]-1);else
			if(st[y]==1)n2=max(n2,f[y]+1);
		}
	}
	if(n1<n2)if(n2==lim)cnt++,f[x]=lim,st[x]=0;else f[x]=n2,st[x]=1;
	else if(n1!=-1)f[x]=n1,st[x]=0;else f[x]=0,st[x]=2;
}
bool ok(int x){cnt=0;dfs(1,0,x);if(st[1]==1)cnt++;return cnt<=m;}
int main(){
	scanf("%d%d",&n,&m);for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(l=0,r=n;l<=r;){
		mid=l+r>>1;
		if(ok(mid))ans=mid,r=mid-1;else l=mid+1;
	}
	printf("%d",ans);
}

T12 Inspection 坚挺了15S WA了,本地数据都过的,真是搞不懂。。大概就是求出每个点到其他点的距离和-到最远点的距离,但是最大子树*2=n时要特判一下,只能从最大子树中找最大直径。。原来是n=1的情况没有忒判

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;
typedef long long LL;LL go[N],ans,now;bool flag;
int n,i,tot,x,y,up[N],ma[N],fa[N],to[N],ne[N<<1],la[N<<1],fir[N],size[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x){
	size[x]=1;to[x]=0;
	for(int i=fir[x];i;i=ne[i]){
		int y=la[i];
		if(fa[x]!=y){
			fa[y]=x;
			dfs(y);
			to[x]=max(to[y]+1,to[x]);
			if(size[y]>ma[x])ma[x]=size[y];
			size[x]+=size[y];
			go[x]+=go[y]+(LL)size[y];
		}
	}
	if(n-size[x]>ma[x])ma[x]=n-size[x];
}
void dfs2(int x){
	if(x!=1)go[x]=go[fa[x]]+(LL)n-(LL)size[x]*2;
	int mx1=-1,mx2=-1,now;
	for(int i=fir[x];i;i=ne[i]){
		int y=la[i];
		if(fa[x]!=y){
			if(to[y]>mx1)mx2=mx1,mx1=to[y];
			else if(to[y]>mx2)mx2=to[y];
		}
	}
	for(int i=fir[x];i;i=ne[i]){
		int y=la[i];
		if(fa[x]!=y){
			if(to[y]==mx1)now=mx2;else now=mx1;
			up[y]=max(up[x]+1,max(1,now+2));
			dfs2(y);
		}
	}
}
int main(){
	scanf("%d",&n);if(n==1)return puts("0"),0;
	for(i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	dfs(1);dfs2(1);
	for(x=1;x<=n;x++)if(ma[x]*2<=n){
		flag=0;now=1e18;
		for(i=fir[x];i;i=ne[i]){
			int y=la[i];
			if(fa[x]==y){
				now=min(now,go[x]*2-(LL)up[x]);
				if(size[x]*2==n){
					flag=1;
					printf("%lld\n",go[x]*2-(LL)up[x]);
					break;
				}
			}else{
				now=min(now,go[x]*2-(LL)to[y]-1);
				if(size[y]*2==n){
					flag=1;
					printf("%lld\n",go[x]*2-(LL)to[y]-1);
					break;
				}
			}
		}
		if(!flag)printf("%lld\n",now);
	}else puts("-1");
}

T13 Meteors 很容易想到二分时间,但是如果二分时间后对每个国家暴力判显然是不可行的,于是需要CDQ分治

以时间为单位分治,每次把mid时间内完成收集的国家放到一边,剩下的放到另一边,用链表存储每个国家的信息,就可以在O(mlgmlgk)之内出解

#include<cstdio>
#define inf 1e9
#define N 333333
typedef long long LL;
int n,m,i,x,k,tot,la[N],ne[N],fir[N],e[N],id[N],L[N],R[N],a[N],ans[N],q1[N],q2[N];
LL sum,cur[N],now[N],c[N];
void add(int x,int z){for(;x<=m;x+=x&-x)c[x]+=z;}
LL que(int x){for(sum=0;x;x-=x&-x)sum+=c[x];return sum;}
void get(int x,int y,int z){add(x,z);add(y+1,-z);}
void solve(int l,int r,int h,int t){
	if(l==r){for(int i=h;i<=t;i++)ans[id[i]]=l;return;}
	int i,j,w,mid=(l+r)>>1,ln=0,rn=0;
	for(i=l;i<=mid;i++)if(L[i]<=R[i])get(L[i],R[i],a[i]);else get(L[i],m,a[i]),get(1,R[i],a[i]);
	for(i=h;i<=t;i++){
		cur[w=id[i]]=0;
		for(j=fir[w];j;j=ne[j]){
			cur[w]+=que(la[j]);
			if(cur[w]+now[w]>e[w])break;
		}
		if(cur[w]+now[w]>=e[w])q1[++ln]=w;else q2[++rn]=w,now[w]+=cur[w];
	}
	for(i=l;i<=mid;i++)if(L[i]<=R[i])get(L[i],R[i],-a[i]);else get(L[i],m,-a[i]),get(1,R[i],-a[i]);
	for(i=1;i<=ln;i++)id[h+i-1]=q1[i];
	for(i=1;i<=rn;i++)id[h+i+ln-1]=q2[i];
	solve(l,mid,h,h+ln-1);solve(mid+1,r,h+ln,t);
}
int main(){
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)scanf("%d",&x),la[++tot]=i,ne[tot]=fir[x],fir[x]=tot;
	for(i=1;i<=n;i++)scanf("%d",&e[i]),id[i]=i;
	scanf("%d",&k);
	for(i=1;i<=k;i++)scanf("%d%d%d",&L[i],&R[i],&a[i]);
	k++;L[i]=1;R[i]=m;a[i]=inf;
	solve(1,k,1,n);
	for(i=1;i<=n;i++)ans[i]==k?puts("NIE"):printf("%d\n",ans[i]);
}

T14 黄主力做了%%%

T15 Sticks 写了个贪心2100MS被鏼死,把贪心改一下,只要从大到小选就A啦哈哈,虽然跑得超慢,但主要原因是写了个set+堆,想不通为什么这道题没有1K以内的。。

#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
set<int>st[52];
int n,i,x,p,rt,sz,a,b,c,val[52],ch[52][2];
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(val[x]<val[y])swap(x,y);
	ch[x][1]=merge(ch[x][1],y);
	swap(ch[x][0],ch[x][1]);
	return x;
}
int main(){
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%d",&x);
		while(x--){
			scanf("%d",&p);
			st[i].insert(p);
		}
		val[i]=*--st[i].end();
		rt=merge(rt,i);sz++;
	}
	while(sz>=3){
		a=rt;rt=merge(ch[rt][0],ch[rt][1]);ch[a][0]=ch[a][1]=0;
		b=rt;rt=merge(ch[rt][0],ch[rt][1]);ch[b][0]=ch[b][1]=0;
		c=rt;rt=merge(ch[rt][0],ch[rt][1]);ch[c][0]=ch[c][1]=0;
		if(val[b]+val[c]>val[a])return printf("%d %d %d %d %d %d",a,val[a],b,val[b],c,val[c]),0;
		rt=merge(rt,c);rt=merge(rt,b);
		st[a].erase(val[a]);
		if(st[a].size())val[a]=*--st[a].end(),rt=merge(rt,a);else sz--;
	}
	puts("NIE");
}

T16 Party 乱写个按度数排序贪心选点的贪心,竟然1A了哈哈。。看了下正解,应该是对不合法点对进行去除就可以了

#include<cstdio>
#include<algorithm>
#define N 3030
#define M 9000200
using namespace std;
int n,m,i,j,x,y,tot,h,fir[N],du[N],a[N],q[N],v[N],ne[M],la[M];
bool flag;bool cmp(int x,int y){return du[x]>du[y];}
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;du[x]++;}
int main(){
	scanf("%d%d",&n,&m);for(i=1;i<=n;i++)a[i]=i;
	for(i=1;i<=m;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	sort(a+1,a+n+1,cmp);q[1]=a[1];h=1;
	for(i=2;i<=n;i++){
		if(h>=(n/3))break;
		for(j=fir[a[i]];j;j=ne[j])v[la[j]]=i;
		flag=1;
		for(j=1;j<=h;j++)if(v[q[j]]!=i){flag=0;break;}
		if(flag)q[++h]=a[i];
	}
	for(i=1;i<=h;i++)printf("%d ",a[i]);
}

T17 Programming Contest 写了个费用流动态加边,但正好T了。。本地的数据都能过。。难道歪果仁都会ZKW费用流吗。。难道我vector作死?

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define inf 1e9
#define N 222222
#define M 2222222
using namespace std;
int n,m,i,he,ta,s,t,R,T,k,x,y,sz,ns,ans,flow,sum,tot,p,to[N],o[510],fir[N],dis[N],pre[N],q[M],la[M],ne[M],cos[M],va[M];
bool v[N];vector<int>a[510];vector<int>::iterator it;char ch;
void read(int &x){
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	for(x=0;ch>='0';ch=getchar())x=x*10+ch-'0';
}
void ins(int x,int y,int fl,int co){
	la[tot]=y;ne[tot]=fir[x];cos[tot]=co;va[tot]=fl;fir[x]=tot++;
	la[tot]=x;ne[tot]=fir[y];cos[tot]=-co;va[tot]=0;fir[y]=tot++;
}
bool spfa(){
	memset(v,0,sizeof(v));
	for(i=1;i<=ns;i++)dis[i]=inf;
	he=0;ta=1;dis[s]=0;v[s]=1;q[1]=s;
	while(he!=ta)
		for(i=fir[x=q[he=he%ns+1]],v[x]=0;i;i=ne[i])
			 if(dis[x]+cos[i]<dis[y=la[i]]&&va[i]){
				dis[y]=dis[x]+cos[pre[y]=i];
				if(!v[y])v[q[ta=ta%ns+1]=y]=1;
			}
	if(dis[t]==inf)return 0;return 1;
}
void end(){
	sum=inf;
	for(i=t;i!=s;i=la[pre[i]^1])if(va[pre[i]]<sum)sum=va[pre[i]];
	for(i=t;i!=s;i=la[pre[i]^1]){
		va[p=pre[i]]-=sum;
		va[p^1]+=sum;
		ans+=sum*cos[p];
	}
	p=to[la[pre[t]^1]];o[p]++;to[++ns]=p;
	if(o[p]<=sz){
		ins(ns,t,1,o[p]);
		for(it=a[p].begin();it!=a[p].end();it++)ins(*it,ns,1,0);
	}
	flow+=sum;
}
int main(){
	read(n);read(m);read(R);read(T);read(k);sz=T/R;s=m+1;ns=t=s+1;tot=2;
	for(i=1;i<=m;i++)ins(s,i,1,0);
	for(i=1;i<=n;i++)to[++ns]=i,o[i]=1,ins(ns,t,1,1);
	for(i=1;i<=k;i++)read(x),read(y),a[x].push_back(y),ins(y,x+m+2,1,0);
	for(;spfa();end());
	printf("%d %d",flow,ans*R);
}

POI2014(15/17)(1077/1090)

T1 Salad Bar 写了个nlg^2n的树状数组单点修改区间最值被鏼死了。。改成nlgn的线段树才卡过。。大概就是单调栈维护出每个点向左、向右最多扩展的点,然后只要一个点的l[j]<=i且i<=l[j]<=r[i]则该点合法,每次把符合条件的点加入统计出i-r[i]中出现的最大值即可更新答案

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1001000
#define inf 1e9
using namespace std;
int n,i,t,ans,tot,x,w[N],c[N],a[N],q[N],r[N],l[N],la[N],ne[N],fir[N];char s[N];
void add(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void ins(int x,int v){
	w[x]=v;
	for(;x<=n;x+=x&-x){
		c[x]=v;
		for(int j=1;j<x&-x;j<<=1)c[x]=max(c[x],c[x-j]);
	}
}
int query(int l,int r){
	int ans=w[r];
	while(1){
		ans=max(ans,w[r]);
		if(l==r)return ans;
		for(r--;r-l>=r&-r;r-=r&-r)ans=max(ans,c[r]);
	}
	return ans;
}
int main(){
	scanf("%d%s",&n,s+2);n++;a[n+1]=-inf;
	for(i=2;i<=n;i++)a[i]=a[i-1]+(s[i]=='p'?1:-1);
	for(i=1;i<=n+1;q[++t]=i++)for(;t&&a[i]<a[q[t]];)r[q[t--]]=i-1;
	for(a[0]=inf,t=0,i=n;i>=0;q[++t]=i--)for(;t&&a[i]>a[q[t]];)l[q[t--]]=i+1;
	for(i=1;i<=n;i++)add(l[i],i);
	for(x=1;x<=n;x++){
		for(i=fir[x];i;i=ne[i])ins(la[i],la[i]);
		ans=max(ans,query(x,r[x])-x);
	}
	printf("%d",ans);
}
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1001000
#define inf 1e9
using namespace std;
int n,i,t,ans,tot,x,a[N],q[N],r[N],l[N],la[N],ne[N],fir[N];char s[N];
struct Tr{int l,r,v;}T[N*4];
void add(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void bt(int k,int l,int r){
	T[k].l=l;T[k].r=r;int mid=l+r>>1;
	if(l==r)return;
	bt(k<<1,l,mid);bt(k<<1|1,mid+1,r);
}
void ins(int k,int x){
	int l=T[k].l,r=T[k].r,mid=l+r>>1;
	if(l==r){T[k].v=l;return;}
	if(x<=mid)ins(k<<1,x);else ins(k<<1|1,x);
	T[k].v=max(T[k<<1].v,T[k<<1|1].v);
}
int query(int k,int x,int y){
	int l=T[k].l,r=T[k].r,mid=l+r>>1;
	if(x<=l&&r<=y)return T[k].v;
	if(x>mid)return query(k<<1|1,x,y);else
	if(y<=mid)return query(k<<1,x,y);else
	return max(query(k<<1,x,mid),query(k<<1|1,mid+1,y));
}
int main(){
	scanf("%d%s",&n,s+2);n++;a[n+1]=-inf;
	for(i=2;i<=n;i++)a[i]=a[i-1]+(s[i]=='p'?1:-1);
	for(i=1;i<=n+1;q[++t]=i++)for(;t&&a[i]<a[q[t]];)r[q[t--]]=i-1;
	for(a[0]=inf,t=0,i=n;i>=0;q[++t]=i--)for(;t&&a[i]>a[q[t]];)l[q[t--]]=i+1;
	for(i=1;i<=n;i++)add(l[i],i);bt(1,1,n);
	for(x=1;x<=n;x++){
		for(i=fir[x];i;i=ne[i])ins(1,la[i]);
		ans=max(ans,query(1,x,r[x])-x);
	}
	printf("%d",ans);
}

T2 Hotel 首先很好想到枚举中心店,然后找到离它距离相等的三个点即为答案,我一开始暴力地写了一遍,发现会出现重复统计的情况,因为三个点可能来自同一个子树中,所以要对所有出边各做一次DFS再统计答案,就能得到正确的结果

#include<cstdio>
#include<cstring>
#define N 5010
typedef long long LL;LL sum,cnt[N],ans[N];
int n,i,j,x,y,tot,dis[N],fir[N],la[N<<1],ne[N<<1];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int fa,int dp){
	++dis[dp];
	for(int i=fir[x];i;i=ne[i])if(la[i]!=fa)dfs(la[i],x,dp+1);
}
int main(){
	scanf("%d",&n);
	for(i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(x=1;x<=n;x++){
		memset(cnt,0,sizeof(cnt));memset(ans,0,sizeof(ans));
		for(i=fir[x];i;i=ne[i]){
			memset(dis,0,sizeof(dis));
			dfs(la[i],x,1);
			for(j=1;j<=n;j++){
				sum+=ans[j]*dis[j];
				ans[j]+=cnt[j]*dis[j];
				cnt[j]+=dis[j];
			}
		}
	}
	printf("%lld",sum);
}

T3 Bricks 写了个贪心爆了好几发,终于A啦

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;
int n,m,i,s,t,x,la,rt,top,ans[N],val[N],son[N][2];
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(val[x]<val[y]||val[x]==val[y]&&y==t)swap(x,y);
	son[x][1]=merge(son[x][1],y);
	swap(son[x][0],son[x][1]);
	return x;
}
int main(){
	scanf("%d%d%d",&n,&s,&t);val[s]--;val[t]--;
	for(i=1;i<=n;i++){
		scanf("%d",&x);
		m+=x,val[i]+=x;
		if(val[i]<0)return puts("0"),0;
		rt=merge(rt,i);
	}
	if(m==1){
		if(s==t&&t==1)return printf("%d",s),0;
		return puts("0"),0;
	}
	ans[++top]=s;
	for(la=s,i=2;i<m;i++){
		if(rt==la){
			x=rt;rt=merge(son[rt][0],son[rt][1]);
			if(--val[la=rt]<0)return puts("0"),0;
			rt=merge(son[rt][0],son[rt][1]);
			son[x][0]=son[x][1]=son[la][0]=son[la][1]=0;
			rt=merge(rt,x);
		}else{
			if(--val[la=rt]<0)return puts("0"),0;
			rt=merge(son[rt][0],son[rt][1]);
			son[la][0]=son[la][1]=0;
		}
		rt=merge(rt,la);ans[++top]=la;
	}
	ans[++top]=t;
	for(i=1;i<=top;i++)printf("%d ",ans[i]);
}

T4 Couriers 写了个莫队T了,复杂度n^1.5lg(n^0.5)。。只好写个主席树,好卡空间啊、、

#include<cstdio>
#include<algorithm>
#define N 501000
using namespace std;
int n,m,i,l,r,a[N],pos[N],ans[N],BS=100;
struct EG{int l,r,z;}eg[N];
struct T{int l,r,v,fm;}t[N*4];
bool cmp(EG a,EG b){if(pos[a.l]==pos[b.l])return a.r<b.r;return a.l<b.l;}
void bt(int k,int l,int r){
	t[k].l=l;t[k].r=r;int mid=l+r>>1;
	if(l==r){t[k].fm=l;return;}
	bt(k<<1,l,mid);bt(k<<1|1,mid+1,r);
}
void ins(int k,int x,int z){
	int l=t[k].l,r=t[k].r,mid=l+r>>1;
	if(l==r){t[k].v+=z;return;}
	if(x<=mid)ins(k<<1,x,z);else ins(k<<1|1,x,z);
	if(t[k<<1].v>t[k<<1|1].v)t[k].v=t[k<<1].v,t[k].fm=t[k<<1].fm;
	else t[k].v=t[k<<1|1].v,t[k].fm=t[k<<1|1].fm;
}
int main(){
	scanf("%d%d",&n,&m);for(i=1;i<=n;i++)scanf("%d",&a[i]),pos[i]=(i-1)/BS+1;
	for(i=1;i<=m;i++)scanf("%d%d",&eg[i].l,&eg[i].r),eg[i].z=i;
	sort(eg+1,eg+m+1,cmp);bt(1,1,n);
	for(i=1,l=1,r=0;i<=m;i++){
		for(;r<eg[i].r;r++)ins(1,a[r+1],1);
		for(;r>eg[i].r;r--)ins(1,a[r],-1);
		for(;l<eg[i].l;l++)ins(1,a[l],-1);
		for(;l>eg[i].l;l--)ins(1,a[l-1],1);
		if(t[1].v>(eg[i].r-eg[i].l+1)/2)ans[eg[i].z]=t[1].fm;
	}
	for(i=1;i<=m;i++)printf("%d\n",ans[i]);
}
#include<cstdio>
#define N 9999999
int n,m,x,y,sz,i,root[500010],ls[N],rs[N],sum[N];
void ins(int l,int r,int x,int &y,int v){
	sum[y=++sz]=sum[x]+1;
	if(l==r)return;int mid=(l+r)>>1;ls[y]=ls[x];rs[y]=rs[x];
	if(v<=mid)ins(l,mid,ls[x],ls[y],v);else ins(mid+1,r,rs[x],rs[y],v);
}
int query(int l,int r,int x,int y,int v){
	if(l==r)return l;int mid=(l+r)>>1;
	if(sum[ls[y]]-sum[ls[x]]>v)return query(l,mid,ls[x],ls[y],v);
	else if(sum[rs[y]]-sum[rs[x]]>v)return query(mid+1,r,rs[x],rs[y],v);
	return 0;
}
int main(){
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)scanf("%d",&x),ins(1,n,root[i-1],root[i],x);
	for(i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		printf("%d\n",query(1,n,root[x-1],root[y],(y-x+1)/2));
	}
}

T6 Card 写了个分块,但一开始T了,调了会参还是T,然后加了个读入优化卡过哈哈,代码长度再次碾压

#include<cstdio>
#include<algorithm>
#define N 200020
using namespace std;
int n,i,x,y,bx,by,n1,n2,now,m,BN,BS=200,down[N],up[N],a[N],b[N];char ch;
void read(int &x){
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	for(x=0;ch>='0';ch=getchar())x=x*10+ch-'0';
}
void get(int x){
	n1=a[(x-1)*BS+1];n2=b[(x-1)*BS+1];
	for(i=(x-1)*BS+2;i<=x*BS&&i<=n;i++){
		if(n1>b[i])n1=1e9;else if(n1>a[i])n1=b[i];else n1=a[i];
		if(n2>b[i])n2=1e9;else if(n2>a[i])n2=b[i];else n2=a[i];
	}
	down[x]=n1;up[x]=n2;
}
int main(){
	read(n);BN=(n-1)/BS+1;
	for(i=1;i<=n;i++){
		read(a[i]);read(b[i]);
		if(a[i]>b[i])swap(a[i],b[i]);
	}
	for(x=1;x<=BN;x++)get(x);scanf("%d",&m);
	while(m--){
		read(x);read(y);bx=(x-1)/BS+1;by=(y-1)/BS+1;
		swap(a[x],a[y]);swap(b[x],b[y]);get(bx);get(by);
		for(now=down[1],i=2;i<=BN;i++)if(now>up[i])now=1e9;else if(now>down[i])now=up[i];else now=down[i];
		if(now==1e9)puts("NIE");else puts("TAK");
	}
}

T7 Around the world 处理出每个点往后的尽量后的点,然后从一个点开始走,每次走尽量后的点,走n步后一定会走到以后优的点。于是从这个优的点出发再走一圈就可以得到答案,时间复杂度O(ns)。

#include<bits/stdc++.h>
int n,s,d,i,j,z,p,w,o,ff,a[1001000],ne[1001000];
int main(){
	for(scanf("%d%d",&n,&s),i=0;i<n;i++)scanf("%d",&a[i]);
	for(;s--;){
		for(scanf("%d",&d),i=j=ff=z=0;i<n;i++){
			for(;z+a[j%n]<=d;j++)z+=a[j%n];
			if(!(ne[i]=j-i))ff=1;z-=a[i];
		}
		if(ff){puts("NIE");continue;}
		for(i=w=p=0;i<n;i++)w=(w+ne[w])%n;
		for(o=w;o<w+n;p++)o+=ne[o%n];
		printf("%d\n",p);
	}
}

T8 Criminals 题目大意:给你一个颜色序列,有两个人会分别从左往右和从右往左走,并在途中任意取走几个格子里面的颜色,直到两个人相遇。已知两个人所取走的颜色序列,并且保证这两个颜色序列的最后一个元素都是同一种颜色,代表两个人相遇的点。还知道两个人出发点的颜色都是相同的,并且他们不会取出发点的颜色(即在两个人的序列中都不会出现)。同时还发现不存在任意一个颜色在这两个序列中出现两次。求出可能的相遇点有多少个,并输出。N<=1000000。

由于每个颜色在序列中只会出现一次 ,于是可以对于每个i找到左边最大的j,使得从j开始到i-1有第一个子序列;找到右边最小的j,使得j到i-1有第二个子序列。通过单调性记录f[i]表示长度为i的子序列的答案,做到O(n)的复杂度。然后枚举相遇点的位置,如果L[i]左边、R[i]右边存在相同颜色就可以,这也可以单调扫一遍做到O(n)复杂度。

#include<bits/stdc++.h>
#define N 1001000
int n,k,x,i,j,m,l,o,t,a[N],c[N],h[N],ne[N],f[N],L[N],R[N],q[N],c1[N],c2[N];
int main(){
	for(scanf("%d%d",&n,&k),i=1;i<=n;i++)scanf("%d",&c[i]);
	for(scanf("%d%d",&m,&l),i=1;i<=m;i++)scanf("%d",&a[i]);
	for(i=m+l-1;i>=m;i--)scanf("%d",&a[i]);
	for(i=1;i<=n;i++)ne[i]=h[c[i]],h[c[i]]=i,f[i]=n+1;
	for(i=n;i;i--)if(R[i]=l>1?f[m+l-1]:i,c[i]==a[m+1])for(f[m+1]=i,j=m+2;j<m+l;j++)for(;(x=f[j]<=n?ne[f[j]]:h[a[j]])>f[j-1];f[j]=x);
	for(i=1;i<=n;i++)h[i]=n+1,f[i]=0;
	for(i=n;i;i--)ne[i]=h[c[i]],h[c[i]]=i;
	for(i=1;i<=n;i++)if(L[i]=m>1?f[1]:i,c[i]==a[m-1])for(f[m-1]=i,j=m-2;j;j--)for(;(x=f[j]?ne[f[j]]:h[a[j]])<f[j+1];f[j]=x);
  	for(i=1;i<=n;i++)if(L[i]>1&&R[i]<n){
  		for(j=n;j>R[i];j--)c2[c[j]]++;
  		for(j=1;j<L[i];j++)if((!c1[c[j]]++)&&c2[c[j]])o++;
  		if(o&&c[i]==a[m])q[++t]=i;break;
	}
	for(i++;i<=n;i++){
		if(R[i]>=n)break;
		for(j=R[i-1];j<R[i];j++)if((!--c2[c[j]])&&c1[c[j]])o--;
		for(j=L[i-1];j<L[i];j++)if((!c1[c[j]]++)&&c2[c[j]])o++;
		if(o&&c[i]==a[m])q[++t]=i;
	}
	for(printf("%d\n",t),i=1;i<=t;i++)printf("%d ",q[i]);
}

T9 FarmCraft 考虑树形DP,对于一个子树x维护两个值d[]和f[]分别表示走完子树的距离和走完这个子树的最少时间,关键是怎么计算f[]

如果第i个走的子树是y,它的贡献是Σsum(d[j]+2)+f[x]+1(j<i),目标就是最小化max(sum(d[j]+2)+f[x]+1)

对于相邻两个子树i,j,如果i比j优max(f[i],d[i]+2+f[j])<max(f[j],d[j]+2+f[i])

f[x]=max(c[x],每个子树的贡献),DFS一遍最后统计答案即可

#include<cstdio>
#include<algorithm>
#define N 500100
using namespace std;
int n,i,x,y,tot,q[N],c[N],d[N],f[N],fir[N],la[N<<1],ne[N<<1];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
bool cmp(int x,int y){return max(f[x],d[x]+2+f[y])<max(f[y],d[y]+2+f[x]);}
void dfs(int x,int fa){
	int i,t=0,sum=0;
	for(int i=fir[x];i;i=ne[i])if(la[i]!=fa)dfs(la[i],x),d[x]+=d[la[i]]+2;
	for(i=fir[x];i;i=ne[i])if(la[i]!=fa)q[++t]=la[i];
	sort(q+1,q+t+1,cmp);
	for(i=1;i<=t;sum+=d[q[i++]]+2)f[x]=max(f[x],sum+f[q[i]]+1);
}
int main(){
	scanf("%d",&n);for(i=1;i<=n;i++)scanf("%d",&c[i]),f[i]=c[i];
	for(i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	dfs(1,0);printf("%d",max(c[1]+d[1],f[1]));
}

T10 Freight T[i]=max(T[i],T[i-1]+1),f[i]=min(max(T[i],f[j]+i-j-1)+s*2+i-j-1)=min(max(-j+T[i],f[j]-2*j+i-1))+s*2+i-1=min(min(-j)+T[i](f[j]-j<=T[i]-i+1),min(f[j]-2*j)+i-1(f[j]-j>=T[i]-i+1))+s*2+i-1,用BIT来维护两个最小值就可以了。

#include<bits/stdc++.h>
#define N 1001000
using namespace std;typedef long long LL;int n,i,x,W;LL y,z,S,c[N],d[N],f[N],T[N],v[N];
void A1(int x,LL z){for(;x;x-=x&-x)c[x]=min(c[x],z);}void A2(int x,LL z){for(;x<=W;x+=x&-x)d[x]=min(d[x],z);}
LL Q1(int x){LL V=1e18;for(;x<=W;x+=x&-x)V=min(V,c[x]);return V;}LL Q2(int x){LL V=1e18;for(;x;x-=x&-x)V=min(V,d[x]);return V;}
int main(){
	for(scanf("%d%lld",&n,&S),T[0]=-1e9,i=1;i<=n;i++)scanf("%lld",&T[i]),T[i]=max(T[i],T[i-1]+1),v[i]=T[i]-i+1;
	for(sort(v+1,v+n+2),W=unique(v+1,v+n+2)-v-1,i=1;i<=W;i++)c[i]=d[i]=1e18;
	for(A1(1,0),A2(1,0),i=1;i<=n;i++){
		x=lower_bound(v+1,v+W+1,T[i]-i+1)-v;y=Q1(x);z=Q2(x);
		f[i]=min(y+i,z+T[i])+S*2+i-1;A1(upper_bound(v+1,v+W+1,f[i]-i)-v-1,f[i]-2*i);A2(lower_bound(v+1,v+W+1,f[i]-i)-v,-i);
	}printf("%lld",f[n]);
}

T11 Little Bird 一开始想nlgn的DP,但发现最短的500B不到,所以觉得不太可能就改成单调队列,维护一个dp值下降的单调队列,dp值相同时取高度高的,这样就可以保证正确性啦

#include<cstdio>
#define N 1001000
int n,i,Q,x,h,t,f[N],a[N],q[N];
int main(){
	scanf("%d",&n);for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(scanf("%d",&Q);Q--;){
		scanf("%d",&x);q[h=t=1]=1;
		for(i=2;i<=n;i++){
			for(;h<=t&&i-q[h]>x;h++);
			f[i]=f[q[h]]+(a[i]>=a[q[h]]);
			for(;h<=t&&(f[i]<f[q[t]]||f[i]==f[q[t]]&&a[i]>=a[q[t]]);t--);
			q[++t]=i;
		}
		printf("%d\n",f[n]);
	}
}

T12 Rally 这题蛮神的,加一个虚拟源点S和一个虚拟汇点T,对所有点进行一遍拓扑DP求出从S到该点的最大距离f[]和该点到T的最大距离g[],那么一条边x->y的权值是f[x]+g[y],这个图的最长链就是所有边边权的最大值

然后拓扑序删点,按照拓扑序依次将该点入边删掉,此时最大值为答案,然后把该点出边加入

我写了个set,顺便还搞懂了multiset的正确姿势,感谢数国队让我代码长度碾压啦

#include<cstdio>
#include<algorithm>
#include<set>
#define N 501000
using namespace std;
multiset<int>st;
int n,m,x,y,h,t,i,j,tot,u,ans,q[N],du[N],fir[N],fir2[N],f[N],g[N],ne[N<<2],la[N<<2];
void ins(int x,int y){du[y]++;la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void ins2(int x,int y){la[++tot]=y;ne[tot]=fir2[x];fir2[x]=tot;}
int main(){
	scanf("%d%d",&n,&m);ans=1e9;
	while(m--)scanf("%d%d",&x,&y),ins(x,y),ins2(y,x);
	for(i=1;i<=n;i++)if(!du[i])q[++t]=i;
	while(h<t)for(i=fir[x=q[++h]];i;i=ne[i])if(!--du[la[i]])q[++t]=la[i];
	for(j=1;j<=n;j++)for(f[x=q[j]]=max(f[x],1),i=fir[x];i;i=ne[i])f[la[i]]=max(f[la[i]],f[x]+1);
	for(j=n;j;j--)for(g[x=q[j]]=max(g[x],1),i=fir[x];i;i=ne[i])g[x]=max(g[x],g[la[i]]+1);
	for(i=1;i<=n;i++)st.insert(g[i]);st.insert(0);
	for(j=1;j<=n;j++){
		for(i=fir2[x=q[j]];i;i=ne[i])st.erase(st.find(f[la[i]]+g[x]));
		st.erase(st.find(g[x]));
		if(*--st.end()<ans)ans=*--st.end(),u=x;
		for(i=fir[x];i;i=ne[i])st.insert(f[x]+g[la[i]]);
		st.insert(f[x]);
	}
	printf("%d %d",u,ans-1);
}

T14 Solar Panels 只需要找到k使得gcd[(a/k)~(b/k),(c/k)~(d/k)]>=1即可,大力容斥一下就行啦

#include<cstdio>
#include<algorithm>
using namespace std;
int T,a,b,c,d,i,j,ans;
int main(){
	for(scanf("%d",&T);T--;){
		scanf("%d%d%d%d",&a,&b,&c,&d);
		if(b>d)swap(a,c),swap(b,d);a--;c--;
		for(i=1;i<=b;i=j+1){
			j=min(b/(b/i),d/(d/i));
			if(b/j>a/j&&d/j>c/j)ans=j;
		}
		printf("%d\n",ans);
	}
}

T15 Supercomputer sum[i]表示深度大于i的点数,ans=max(i+(sum[i]/k)),现在有Q个k值要一一给出答案,可以利用单调性维护答案就可以在O(n)时间内出解

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;typedef long long LL;
int n,Q,i,x,u,tot,h,t,q[N],a[N],sum[N],cnt[N],fir[N],la[N],ne[N],ans[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int fa,int hi){
	cnt[hi]++;u=max(u,hi);
	for(int i=fir[x];i;i=ne[i])if(la[i]!=fa)dfs(la[i],x,hi+1);
}
int ceil(int a,int b){return a/b+(a%b>0);}
int main(){
	scanf("%d%d",&n,&Q);for(i=1;i<=Q;i++)scanf("%d",&a[i]);
	for(i=2;i<=n;i++)scanf("%d",&x),ins(x,i);
	dfs(1,0,1);for(i=u;i;i--)sum[i]=sum[i+1]+cnt[i+1];
	for(h=1,i=u;i;q[++t]=i--)for(;h<t&&(LL)(sum[i]-sum[q[t]])*(q[t-1]-q[t])>=(LL)(sum[q[t-1]]-sum[q[t]])*(i-q[t]);t--);
	for(i=1;i<=u;i++)printf("%d\n",sum[i]);
	for(i=n;i;i--){
		for(;h<t&&(LL)i*(q[h]-q[h+1])<=sum[q[h+1]]-sum[q[h]];h++);
		ans[i]=q[h]+ceil(sum[q[h]],i);
	}
	for(i=1;i<=Q;i++)printf(i<Q?"%d ":"%d",a[i]>n?u:ans[a[i]]);
}

T16 Tourism 题目大意:一张n个点m条边的无向图,每个点有一个权值Ci,选出权值和最小的若干关键点,使得任意点离最近关键点的距离不超过1。任意两点间不存在节点数超过10的简单路径,n<=20000,m<=25000。

先DFS出一颗树,树深度不超过10,且非树边都是前向边。
考虑dp,f(x)[i][j]表示在x点,深度为i,状态为j的最小代价。0表示没选,1表示没选且没相邻关键点,2表示没选且有相邻关键点。 
找出所有该点的祖先,考虑转移:
1、不选该点,那么若其祖先有一个点选了,f[i][j+2*pw[i]]=min(f[i][j+2*pw[i]],f[i-1][j]);
2、选该点,那么其祖先状态为1的都可以改为2,f[i][newj]=min(f[i][newj],f[i-1][j]+co[x])。
然后继续DFS,递归完后用儿子来更新祖先,f[i][j]=min(f[i+1][j],f[i+1][j+2*pw[i+1]])。
时间复杂度O((n+m)*3^10),空间复杂度O(10*3^10)。
#include<bits/stdc++.h>
#define N 20020
using namespace std;bool v[N];
int n,m,i,x,y,A,tot,f[15][N*3],d[N],fir[N],la[N*3],ne[N*3],q[N],a[N],pw[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int G(int x,int y){return x/pw[y]%3;}
void dfs(int x,int z){
	v[x]=1;d[x]=z;int i,y,S,t=0,A,B;
	if(!z)f[z][0]=a[x],f[z][1]=0,f[z][2]=2e9;else{
		for(i=fir[x];i;i=ne[i])if(y=la[i],v[y]&&d[y]<z)q[++t]=d[y];
		for(S=0;S<pw[z+1];S++)f[z][S]=2e9;
		for(S=0;S<pw[z];S++){
			for(A=i=1,B=S;i<=t;i++)if(!G(S,q[i]))A=2;else if(G(S,q[i])==1)B+=pw[q[i]];
			f[z][S+A*pw[z]]=min(f[z][S+A*pw[z]],f[z-1][S]);f[z][B]=min(f[z][B],f[z-1][S]+a[x]);
		}
	}
	for(i=fir[x];i;i=ne[i])if(!v[y=la[i]])for(dfs(y,z+1),S=0;S<pw[z+1];S++)f[z][S]=min(f[z+1][S],f[z+1][S+2*pw[z+1]]);
}
int main(){
	for(pw[0]=i=1;i<12;i++)pw[i]=pw[i-1]*3;
	for(scanf("%d%d",&n,&m),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(;m--;ins(x,y),ins(y,x))scanf("%d%d",&x,&y);
	for(i=1;i<=n;i++)if(!v[i])dfs(i,0),A+=min(f[0][0],f[0][2]);
	printf("%d",A);
}

T17 Ant colony 从食蚁兽所在的边开始DFS,维护两个值[l,r]表示蚂蚁数量的可行范围,在叶子节点时二分查找所在区间即可

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;typedef long long LL;
int n,m,k,i,x,y,tot,fir[N],du[N],a[N],la[N<<1],ne[N<<1];
LL ans,inf=2e9;void ins(int x,int y){du[x]++;la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int fa,LL l,LL r){
	if(du[x]==1)ans+=(LL)k*((upper_bound(a+1,a+m+1,(int)r)-a)-(lower_bound(a+1,a+m+1,(int)l)-a));
	for(int i=fir[x];i;i=ne[i])if(la[i]!=fa)dfs(la[i],x,min(l*max(du[la[i]]-1,1),inf),min((r+1)*max(du[la[i]]-1,1)-1,inf));
}
int main(){
	scanf("%d%d%d",&n,&m,&k);for(i=1;i<=m;i++)scanf("%d",&a[i]);sort(a+1,a+m+1);
	for(i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	dfs(la[1],la[2],min((LL)k*max(du[la[1]]-1,1),inf),min((LL)(k+1)*max(du[la[1]]-1,1)-1,inf));
	dfs(la[2],la[1],min((LL)k*max(du[la[2]]-1,1),inf),min((LL)(k+1)*max(du[la[2]]-1,1)-1,inf));
	printf("%lld",ans);
}

POI2000(11/11)(451/451)

T1 迷宫的墙 暴力从后往前判断是否存在相同的即可,相同的话就更新答案

#include<cstdio>
#include<cstring>
#include<map>
#define N 6010
using namespace std;
int n,i,j,ans,w[N],c[N],a[N][3];char s[9];
long long now;map<long long,int>mp;
int main(){
	scanf("%d",&n);w[n]=n;ans=n;
	for(i=1;i<n;i++){
		scanf("%s",s);w[i]=i;
		if(s[0]=='C')c[i]=1;else if(s[0]=='Z')c[i]=2;else c[i]=3;
		for(j=0;j<3;j++)scanf("%d",&a[i][j]);
	}
	for(i=n-1;i;i--){
		now=c[i];
		for(j=0;j<3;j++)a[i][j]=w[a[i][j]],now=now*n+a[i][j]-1;
		if(mp[now])w[i]=mp[now],ans--;else mp[now]=i;
	}
	printf("%d",ans);
}

T2 建造酿酒厂 首先向两边送的分割点肯定是单调的,于是可以O(n)转一圈,每次得到这个分割点,然后通过前缀和快速得到答案,为了方便可以把一个环剪成2*n的链

#include<cstdio>
#include<algorithm>
#define N 20010
using namespace std;
int n,i,j,x,y,z[N],d[N];
long long f[N],g[N],s[N],now,ans=1e18;
int main(){
	scanf("%d",&n);for(i=1;i<=n;i++)scanf("%d%d",&z[i],&d[i]),f[i]=f[i-1]+z[i],g[i]=g[i-1]+d[i],s[i]=s[i-1]+z[i]*g[i-1];
	for(i=n+1;i<=n*2;i++)z[i]=z[i-n],d[i]=d[i-n],f[i]=f[i-1]+z[i],g[i]=g[i-1]+d[i],s[i]=s[i-1]+z[i]*g[i-1];
	for(i=j=1;i<=n;i++){
		for(;(g[j-1]-g[i-1])<=(g[n]>>1)&&j<=n*2;j++);
		now=s[j-1]-s[i]+g[i-1]*f[i]-(f[j-1]-f[i])*g[i-1]+(g[i-1]+g[n])*(f[n]-f[j-1])-s[n]+s[j-1]-s[i];
		if(now<ans)ans=now;
	}
	printf("%lld",ans);
}

T3 病毒 AC自动机弄出所有danger节点后判一遍是否有环即可

#include<cstdio>
#define N 33333
int n,i,j,c,x,y,k,h,t,cnt,son[N][2],fail[N],q[N],v[N];
bool dg[N],flag;char s[N];
void dfs(int x){
	if(dg[x]||flag)return;v[x]=1;
	for(int i=0;i<2;i++){
		int y=son[x][i];
		if(v[y]==1){flag=1;return;}
		if(!v[y])dfs(y);
		if(flag)return;
	}
	v[x]=2;
}
int main(){
	for(scanf("%d",&n),cnt=son[0][0]=son[0][1]=1;n--;dg[x]=1){
		scanf("%s",s);x=1;
		for(i=0;s[i];x=son[x][c],i++)if(!son[x][c=s[i]-'0'])son[x][c]=++cnt;
	}
	for(q[t=1]=1;h<t;)
		for(x=q[++h],i=0;i<2;i++){
			y=son[x][i];
			if(!y){son[x][i]=son[fail[x]][i];continue;}
			for(k=fail[x];!son[k][i];k=fail[k]);
			fail[y]=son[k][i];
			dg[y]|=dg[fail[y]];
			q[++t]=y;
		}
	dfs(1);flag?puts("TAK"):puts("NIE");
}

T4 滑雪 就是一个小小的网络流,由于边的流量只有1,可以直接DFS解决。。

#include<cstdio>
#include<cstring>
#define N 10010
#define M 200020
int n,i,j,k,x,tot,ans,fir[N],pre[N],va[M],la[M],ne[M];bool v[N];
void ins(int x,int y){
	la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;va[tot]=1;
	la[++tot]=x;ne[tot]=fir[y];fir[y]=tot;va[tot]=0;
}
bool dfs(int x){
	v[x]=1;if(x==n)return 1;
	for(int i=fir[x];i;i=ne[i])
		if(va[i]&&!v[la[i]]){
			pre[la[i]]=i;
			if(dfs(la[i]))return 1;
		}
	return 0;
}
int main(){
	scanf("%d",&n);tot=1;
	for(i=1;i<n;i++){
		scanf("%d",&k);ins(i,i+n);
		for(j=1;j<=k;j++)scanf("%d",&x),ins(i+n,x);
	}
	for(;dfs(n+1);ans++){
		for(i=n;i!=n+1;i=la[pre[i]^1])va[pre[i]]^=1,va[pre[i]^1]^=1;
		memset(v,0,sizeof(v));
	}printf("%d",ans);
}

T5 条纹 直接暴力求SG函数就可以了

#include<cstdio>
#include<cstring>
int a,b,c,i,j,x,Q,sg[1111],hash[1111];
int main(){
	scanf("%d%d%d",&a,&b,&c);
	for(i=1;i<=1000;i++){
		memset(hash,0,sizeof(hash));
		if(i>=a)for(j=0;j<=i-a;j++)hash[sg[j]^sg[i-a-j]]=1;
		if(i>=b)for(j=0;j<=i-b;j++)hash[sg[j]^sg[i-b-j]]=1;
		if(i>=c)for(j=0;j<=i-c;j++)hash[sg[j]^sg[i-c-j]]=1;
		for(j=0;j<=1000;j++)if(!hash[j]){sg[i]=j;break;}
	}
	for(scanf("%d",&Q);Q--;sg[x]?puts("1"):puts("2"))scanf("%d",&x);
}

T6 担保 只要判断一下是否有大于等于两个长官担保即可

#include<cstdio>
#define N 510
int n,i,k,x,tot,f,g[N],v[N],s[N],fir[N],ne[N*N],la[N*N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int z){
	v[x]=z;s[x]++;
	for(int i=fir[x];i;i=ne[i])if(v[la[i]]!=z)dfs(la[i],z);
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)for(scanf("%d",&g[i]),k=g[i];k--;)scanf("%d",&x),ins(x,i);
	for(i=1;i<=n;i++)if(!g[i])dfs(i,i);
	for(i=1;i<=n;i++)if(g[i]&&s[i]<2)printf("%d\n",i),f=1;
	if(!f)puts("BRAK");
}

T7 同构 首先如果有偶数环肯定是不合法的,因为这样置换会换到自己爆炸,然后奇数环对答案的贡献是2^((cnt-1)/2)

奇数环之间的贡献是他们的gcd之和,但暴力求可能会T,可以用计数的思想计出每种个数的数量,然后统计贡献时可以在nlgn统计出来

#include<cstdio>
#define N 10010
int n,i,j,x,cnt,ans,tot,w,p,a[N],f[N],to[N];bool v[N];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&to[i]);
	for(i=1;i<=n;i++)if(!v[i]){
		for(v[x=i]=1,cnt=1;!v[to[x]];v[x=to[x]]=1)cnt++;
		if(cnt%2==0)return puts("0"),0;
		p+=cnt-1;a[cnt]++;
	}
	for(i=n;i>=1;i--){
        for(tot=a[i],j=i+i;j<=n;j+=i)tot+=a[j],f[i]+=f[j];
        f[i]=tot*tot-f[i];p+=(f[i]-a[i])*i;
    }
    for(p>>=1,ans=1,w=2;p;w=w*w%1000,p>>=1)if(p&1)ans=ans*w%1000;
    printf("%d",ans);
}

T8请不要提交!

T9 代码 递推后再递归找答案就行了

#include<cstdio>
int i,j,n,k,ln,f[21];char s[21];
void dfs(int l,int r,int k){
    int i;for(i=l;i<=r;i++)if(f[i-l]*f[r-i]<=k)k-=f[i-l]*f[r-i];else break;
    s[ln++]=i+'a'-1;if(i>l)dfs(l,i-1,k/f[r-i]);if(i<r)dfs(i+1,r,k%f[r-i]);
}
int main(){
	scanf("%d%d",&n,&k);n--;
	for(f[0]=i=1;i<=k;i++)for(j=0;j<i;j++)f[i]+=f[j]*f[i-j-1];
	dfs(1,k,n);printf("%s",s);
}

T10 气垫船 首先找出出现次数最多的数,然后判断一下是否在一边没有数,有的话n++,然后判断一下出现次数最多的数*2是否小于等于n即可

#include<cstdio>
int T,n,p,s,i,a[1001000];bool f1,f2;char ch;
void read(int &x){for(ch=getchar();ch<'0';ch=getchar());for(x=0;ch>='0';ch=getchar())x=x*10+ch-'0';}
int main(){
	for(read(T);T--;){
		read(n);p=f1=f2=0;s=1;
		for(i=1;i<=n;i++){
			read(a[i]);
			if(a[i]==p)s++;else s--;
			if(!s)s++,p=a[i];
		}
		for(s=0,i=1;i<=n;i++)if(a[i]>p)f1=1;else if(a[i]<p)f2=1;else s++;
		if(!f1||!f2)n++;
		s*2<=n?puts("TAK"):puts("NIE");
	}
}

T11 公共串 多串LCM后缀自动机裸题,先对一个串做SAM,然后每个串进去匹配,对于每种状态保留最大值,然后取最大值中的最小值,最后再在每种状态中取最大值

#include<cstdio>
#include<cstring>
#include<iostream>
#define N 222222
using namespace std;
int la,S,i,n,cnt,tmp,p,ans,c,np,q,nq,Q,mn[N],fa[N],step[N],v[N],w[N],cl[N],son[N][26];
char s[N];
void add(int x){
    c=s[x]-'a',p=la,la=np=++cnt;step[np]=step[p]+1;
    for(;p&&!son[p][c];p=fa[p])son[p][c]=np;
    if(!p)fa[np]=S;else{
        q=son[p][c];
        if(step[p]+1==step[q])fa[np]=q;else{
            nq=++cnt;step[nq]=step[p]+1;
            memcpy(son[nq],son[q],sizeof son[q]);
            fa[nq]=fa[q];fa[np]=fa[q]=nq;
            for(;son[p][c]==q;p=fa[p])son[p][c]=nq;
        }
    }
}
int main(){
	scanf("%d",&Q);Q--;
    scanf("%s",s);n=strlen(s);la=S=++cnt;
    for(i=0;i<n;i++)add(i);
    for(i=1;i<=cnt;i++)mn[i]=step[i];
    for(i=1;i<=cnt;i++)v[step[i]]++;
    for(i=1;i<=n;i++)v[i]+=v[i-1];
    for(i=1;i<=cnt;i++)w[v[step[i]]--]=i;
    while(Q--){
    	scanf("%s",s);n=strlen(s);memset(cl,0,sizeof(cl));tmp=0;
        for(p=1,i=0;i<n;i++){
            c=s[i]-'a';
            if(son[p][c])p=son[p][c],tmp++;else{
                while(p&&!son[p][c])p=fa[p];
                if(!p)p=1,tmp=0;else tmp=step[p]+1,p=son[p][c];
            }
            cl[p]=max(cl[p],tmp);
        }
        for(i=cnt;i;i--){
            int t=w[i];
            mn[t]=min(mn[t],cl[t]);
            if(cl[t]&&fa[t])cl[fa[t]]=step[fa[t]];
        }
    }
    for(i=1;i<=cnt;i++)ans=max(ans,mn[i]);
    printf("%d",ans);
}

T12 促销 直接set水过

#include<cstdio>
#include<set>
using namespace std;
multiset<int>st;
int n,k,x;long long ans;
int main(){
	for(scanf("%d",&n);n--;){
		for(scanf("%d",&k);k--;)scanf("%d",&x),st.insert(x);
		ans+=(long long)*--st.end()-*st.begin();
		st.erase(st.begin());st.erase(--st.end());
	}
	printf("%lld",ans);
}

感觉这届好水好无聊啊、、

POI2009(10/15)(1127/1183)

T1 石子游戏Kam 就是一个阶梯取石子,阶梯取子就是算奇数位石子异或和,这个就是把每两位差算异或和。。

#include<cstdio>
int T,n,i,ans,a[1010];
int main(){
	for(scanf("%d",&T);T--;){
		scanf("%d",&n);ans=0;
		for(i=1;i<=n;i++)scanf("%d",&a[i]);
		for(i=n;i>0;i-=2)ans^=a[i]-a[i-1];
		ans?puts("TAK"):puts("NIE");
	}
}

T2 救火站Gas 用f[i][j]表示在第i个点距离在j以内的未被覆盖点的个数,g[i][j]表示在第i个点已经走了j步的消防站最多还能覆盖多少点,由于树形结构满足拓扑序,就可以在O(nk)的时间内相互转移出解了

#include<cstdio>
#define N 100100
int n,s,k,x,y,i,j,now,tot,ans,f[N][21],g[N][21],fir[N],ne[N<<1],la[N<<1];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int get(int x){return x?(x-1)/s+1:0;}
int cal(int &x,int &y){if(x>=y)x-=y,y=0;else y-=x,x=0;}
void dfs(int x,int fa){
	int i,j,y;
	for(i=fir[x];i;i=ne[i])if((y=la[i])!=fa){
		dfs(y,x);
		for(j=1;j<=k;j++){
			f[x][j]+=f[y][j-1];
			g[x][j]+=g[y][j-1];
			if(g[x][j]>n)g[x][j]=n;
		}
	}
	ans+=get(f[x][k]);f[x][0]=1;g[x][0]=get(f[x][k])*s;
	for(i=k;i>=0;i--){
		cal(f[x][k-i],g[x][i]);
		if(k>i)cal(f[x][k-i-1],g[x][i]);
	}
}
int main(){
	for(scanf("%d%d%d",&n,&s,&k),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	dfs(1,0);
	for(i=k;i>=0;i--)for(j=k-i;j>=0;j--)cal(f[1][j],g[1][i]);
	for(i=0;i<=k;i++)now+=f[1][i];
	printf("%d",ans+get(now));
}

T3 PRZ 采用记忆化搜索+哈希的方法,每次记录当前状态[p,q,l,r]的哈希值,可以对[p,q%11]开一个链表,哈希值为l*100003+r,然后每次都暴力转移就可以了,状态数5000000左右,HASH是可以过的

#include<cstdio>
#include<cstring>
#define N 100100
#define M 5005000
int T,n,m,i,x,tp,a[N],b[N],st[N][11],c[101],d[101],ne[M],w[M];bool u[M],ff;
int get(int p,int q,int l,int r){
	int &f=st[p][q%11],i,x,y,id;
	for(i=f;i;i=ne[i])if(w[i]==l*100003+r)return u[i];
	id=++tp;ne[id]=f;f=id;w[id]=l*100003+r;
	if(p==q&&l==r)return u[id]=1;
	for(i=p;;i++)if(!--c[a[i]]){x=i;break;}
	for(i=l;;i++)if(!--d[b[i]]){y=i;break;}
	if(a[x]!=b[y])return u[id]=0;
	if(!get(x+1,q,y+1,r))return u[id]=0;
	for(i=p;i<=x;i++)c[a[i]]++;for(i=l;i<=y;i++)d[b[i]]++;
	for(i=q;;i--)if(!--c[a[i]]){x=i;break;}
	for(i=r;;i--)if(!--d[b[i]]){y=i;break;}
	if(a[x]!=b[y])return u[id]=0;
	if(!get(p,x-1,l,y-1))return u[id]=0;
	for(i=q;i>=x;i--)c[a[i]]++;for(i=r;i>=y;i--)d[b[i]]++;
	return u[id]=1;
}
int main(){
	for(scanf("%d",&T);T--;ff=0){
		memset(c,0,sizeof(c));memset(d,0,sizeof(d));memset(st,0,sizeof(st));tp=0;
		for(scanf("%d%d",&n,&m),i=1;i<=n;i++)
		scanf("%d",&x),x==a[i-1]?i--,n--:a[i]=x;
		for(i=1;i<=m;i++)scanf("%d",&x),x==b[i-1]?i--,m--:b[i]=x;
		for(i=1;i<=n;i++)c[a[i]]++;for(i=1;i<=m;i++)d[b[i]]++;
		for(i=1;i<=100;i++)if((c[i]>0)^(d[i]>0))ff=1;
		ff?puts("0"):printf("%d\n",get(1,n,1,m));
	}
}

T4 SLO 首先可以把要交换的用置换表示,然后考虑怎样才能让代价最小。有一种方法是在置换内找一个最小值v,然后让这个最小值和整个置换所有元素各交换一遍,这样的代价是sum+v*(sz-2);还有一种方法是让置换内的最小值和置换外面的一个最小值mv交换,然后让这个mv把置换内其他元素交换到对应位置,再把原来的v换回来,这样的代价是sum+v+mv*(sz+1),对于每个置换在两种方法中取最小值即可

#include<cstdio>
#include<algorithm>
#define N 1001000
#define inf 1e9
using namespace std;typedef long long LL;LL sum,ans;
int n,i,p,top,sz,miv,now,a[N],b[N],c[N],w[N],num[N];bool v[N];
int main(){
	scanf("%d",&n);miv=inf;
	for(i=1;i<=n;i++)scanf("%d",&w[i]),miv=min(miv,w[i]);
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<=n;i++)scanf("%d",&b[i]),num[b[i]]=i;
	for(i=1;i<=n;i++)a[i]=num[a[i]],c[num[i]]=w[i];
	for(i=1;i<=n;i++)if(!v[i]){
		for(now=inf,sum=0,v[p=i]=sz=1;!v[a[p]];v[p=a[p]]=1)sz++,sum+=(LL)c[p],now=min(now,c[p]);
		sum+=(LL)c[p],now=min(now,c[p]);
		ans+=min(sum+(LL)(sz-2)*now,sum+(LL)now+(LL)miv*(sz+1));
	}
	printf("%lld",ans);
}

T6 Kon 用f[i][j]表示在第i-i+1个站的旅途中,检查j次票能检查的最多人次,则f[i][j]=max(f[u][j-1]+sum[i][n]+sum[u][i]-sum[u][n]-sum[i][i]),这样就可以在n^2k时间内出解,这题要输出最小字典序,再保留一下路径就可以了

#include<cstdio>
#include<cstring>
#define N 610
int n,k,i,j,u,tmp,ans,pos,a[N][N],f[N][N],g[N][N],ot[N];
int get(int x1,int y1,int x2,int y2){return a[x2][y2]+a[x1][y1]-a[x1][y2]-a[x2][y1];}
int main(){
	scanf("%d%d",&n,&k);
	for(i=1;i<=n;i++)for(j=i+1;j<=n;j++)scanf("%d",&a[i][j]);
	for(i=1;i<=n;i++)for(j=1;j<=n;j++)a[i][j]+=a[i][j-1];
	for(i=1;i<=n;i++)for(j=1;j<=n;j++)a[i][j]+=a[i-1][j];
	memset(f,0xef,sizeof(f));f[0][0]=0;
	for(i=1;i<=n;i++)
		for(j=1;j<=k;j++){
			for(u=0;u<i;u++){
				tmp=get(u,i,i,n);
				if(f[u][j-1]+tmp>f[i][j]){
					f[i][j]=f[u][j-1]+tmp;
					g[i][j]=u;
				}
			}
			if(j==k&&f[i][j]>ans)ans=f[i][j],pos=i;
		}
	for(i=k;i;i--)ot[i]=pos,pos=g[pos][i];
	for(i=1;i<=k;i++)printf(i<k?"%d ":"%d",ot[i]);
}

T8 Lyz 首先根据Hall定理,有解的条件是任意一个集合的人数≤连接鞋子数量,这个集合是一个连续子序列,则ti=(r-l+1+d)*k,令ti'=ti-k,则所有Σti'<=d*k,用线段树维护出最大子序列然后判断即可

#include<cstdio>
#include<algorithm>
#define L k<<1
#define R k<<1|1
using namespace std;typedef long long LL;
int n,m,K,D,x,y;struct T{int l,r;LL ls,rs,ss,sum;}t[800000];
void ps(int k){
	t[k].ls=max(t[L].ls,t[L].sum+t[R].ls);
	t[k].rs=max(t[R].rs,t[R].sum+t[L].rs);
	t[k].ss=max(max(t[L].ss,t[R].ss),t[L].rs+t[R].ls);
	t[k].sum=t[L].sum+t[R].sum;
}
void bt(int k,int l,int r){
	t[k].l=l;t[k].r=r;int mid=l+r>>1;
	if(l==r){t[k].ls=t[k].rs=t[k].ss=t[k].sum=(LL)-K;return;}
	bt(L,l,mid);bt(R,mid+1,r);ps(k);
}
void ins(int k,int x,LL z){
	int l=t[k].l,r=t[k].r,mid=l+r>>1;
	if(l==r){t[k].ls+=z,t[k].rs+=z,t[k].ss+=z,t[k].sum+=z;return;}
	x<=mid?ins(L,x,z):ins(R,x,z);ps(k);
}
int main(){
	scanf("%d%d%d%d",&n,&m,&K,&D);
	for(bt(1,1,n);m--;){
		scanf("%d%d",&x,&y);ins(1,x,y);
		puts(t[1].ss<=(LL)D*K?"TAK":"NIE");
	}
}

T11 最短回文路 很容易想到DP,用f[i][j]表示i-j是回文路的最短长度,然后直接BFS就可以了,不过这样复杂度是576n^2的,会T

考虑用g[i][j][c]表示i-j是由一个回文路加一个字符c组成的最短长度,然后两个互相推就可以在26n^2的时间内出解

为了加快速度,可以把字符反向存在vector里,这样判相等字符时就可以直接取出了

#include<cstdio>
#include<cstring>
#include<vector>
#define N 410
#define M 120100
using namespace std;
vector<int>a[N][26];vector<int>::iterator it;
struct P{int x,y,z;}q[M*32];char s[9];
int n,m,i,h,t,tot,x,y,z,Q,as,f[N][N],g[N][N][26],fir[N],va[M],la[M],ne[M];
void ins(int x,int y,int z){la[++tot]=y;ne[tot]=fir[x];va[tot]=z;fir[x]=tot;}
int main(){
	scanf("%d%d",&n,&m);memset(f,-1,sizeof(f));memset(g,-1,sizeof(g));
	for(i=1;i<=n;i++)f[i][i]=0,q[++t]=P{i,i,-1};
	for(i=1;i<=m;i++){
		scanf("%d%d%s",&x,&y,s);
		ins(x,y,s[0]-'a');a[y][s[0]-'a'].push_back(x);
		if(~f[x][y])continue;f[x][y]=1;
		q[++t]=P{x,y,-1};
	}
	while(h<t){
		x=q[++h].x;y=q[h].y;z=q[h].z;
		if(z==-1){for(i=fir[y];i;i=ne[i])if(g[x][la[i]][va[i]]==-1)g[x][la[i]][va[i]]=f[x][y]+1,q[++t]=P{x,la[i],va[i]};
		}else for(it=a[x][z].begin();it!=a[x][z].end();it++)if(f[*it][y]==-1)f[*it][y]=g[x][y][z]+1,q[++t]=P{*it,y,-1};
	}
	for(scanf("%d",&Q);Q--;as=x){
		scanf("%d",&x);
		if(as)printf("%d\n",f[as][x]);
	}
}

T12 Wie 把每个点拆成2^p个点然后跑最短路就可以得到答案了,不过这边数似乎可能会爆?反正过了就不管了

#include<cstdio>
#include<cstring>
#define M 7001000
int n,m,p,k,S,x,y,z,Q,w,h,t,i,j,tot,ans,q[M],d[M],fir[M],la[M],va[M],ne[M],s[220];bool v[M];
void ins(int x,int y,int z){la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;}
int main(){
	for(scanf("%d%d%d%d",&n,&m,&p,&k),S=1<<p,i=1;i<=k;i++)
		for(scanf("%d%d",&w,&Q);Q--;)scanf("%d",&x),s[w]|=1<<(x-1);
	for(i=1;i<=n;i++)for(j=0;j<S;j++)ins(i*S+j,i*S+(j|s[i]),0);
	for(;m--;){
		for(scanf("%d%d%d%d",&x,&y,&z,&Q),p=0;Q--;)scanf("%d",&w),p|=1<<(w-1);
		for(i=0;i<S;i++)if((i&p)==p)ins(x*S+i,y*S+i,z),ins(y*S+i,x*S+i,z);
	}
	for(memset(d,63,sizeof(d)),q[t=1]=S,v[S]=1,d[S]=0;h^t;)
        for(i=fir[x=q[h=h%4000000+1]],v[x]=0;i;i=ne[i])if(d[x]+va[i]<d[y=la[i]]){
            d[y]=d[x]+va[i];
            if(!v[y])v[q[t=t%4000000+1]=y]=1;
        }
    for(ans=1e9,i=0;i<S;i++)if(d[n*S+i]<ans)ans=d[n*S+i];
    printf("%d",ans==1e9?-1:ans);
}

T14 Slw 首先很容易发现这是一个斐波那契串,不过这并没有什么卵用。。

考虑对a[i]进行逆变换,相当于把所有ai减1,然后对于ai=0的情况popoqqq就行啦。。

#include<cstdio>
int n,T,i,tmp,a[100100];
bool Solve(){
    while(n>1){
        if(!a[1])a[1]=2;
        if(a[n]==1)n--;else if(a[n]==3)a[n]=2;
        for(i=n;i;i--)if(!a[i])if(a[i-1]==1)a[i-1]=2,a[i]=-1;else if(a[i-1]==3)a[i-1]=2,a[i]=2;else return 0;
        for(tmp=0,i=1;i<=n;i++)if(a[i]!=-1)a[++tmp]=a[i];
        for(n=tmp,i=1;i<=n;i++)a[i]--;
    }
    return 1;
}
int main(){
    for(scanf("%d",&T);T;T--){
        scanf("%d",&n);
        for(i=1;i<=n;i++)scanf("%d",&a[i]);
        puts(Solve()?"TAK":"NIE");
    }
}

T15 Tab 可以发现不管怎么换在同一行的还是在同一行,在同一列的还是在同一列,于是只要判断每行每列的元素是否相同就可以
可以先求出每一行的哈希值,然后排序,排序后再求出每一列的哈希值,然后再排序得到最终哈希值,判断是否相等即可。。

#include<cstdio>
#include<algorithm>
#define N 1010
#define S1 999911657 
#define S2 999911659
using namespace std;typedef unsigned long long LL;
int i,j,n,m,T,a[N][N],b[N][N],e[N];LL ans,d[N];
struct EG{LL x;int z;}c[N];bool cmp(EG a,EG b){return a.x<b.x;}
LL cal(){
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++)scanf("%d",&a[i][j]),e[j]=a[i][j];
		sort(e+1,e+m+1);c[i].z=i;c[i].x=0ull;
		for(j=1;j<=m;j++)(c[i].x*=S1)+=e[j];
	}
	sort(c+1,c+n+1,cmp);for(i=1;i<=n;i++)for(j=1;j<=m;j++)b[i][j]=a[c[i].z][j];
	for(j=1;j<=m;j++)for(d[j]=0,i=1;i<=n;i++)(d[j]*=S1)+=b[i][j];
	sort(d+1,d+m+1);ans=0;for(i=1;i<=m;i++)(ans*=S2)+=d[i];return ans;
}
int main(){for(scanf("%d",&T);T--;puts(cal()==cal()?"TAK":"NIE"))scanf("%d%d",&n,&m);}

这届好难啊TAT,先坑着咯

POI2005(13/16)(1865/1905)

T1 Pra-Dextrogyrate camel 把所有点从2号点开始按关于1号点的极角排序,然后用dp[i][j]表示从1出发走到j,最后一条路是(i,j),然后暴力转移是n^3的,把所有点关于i号点按极角排序,则对于所有转移f[ax][i]-->f[i][bx],ax和bx都是单调递增的,就可以n^2转移了,注意把不合法情况叉掉

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2222
using namespace std;
int n,i,j,t1,t2,h,t,v,ans,dp[N][N],sa[N],sb[N];
struct P{int x,y;}p[N];
int get(P x,P y,P z){return (y.x-x.x)*(z.y-x.y)-(y.y-x.y)*(z.x-x.x);}
bool cmp(int x,int y){return get(p[i],p[x],p[y])<0;}
int w(int x){return x<0?-1:x>0?1:0;}
bool cmp2(P x,P y){
	int a=get(p[1],p[2],x),b=get(p[1],p[2],y);
	return (w(a)*w(b)<0)?a<b:get(p[1],x,y)<0;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d",&p[i].x,&p[i].y);
	for(memset(dp,-1,sizeof(dp)),sort(p+3,p+n+1,cmp2),dp[1][2]=1,i=2;i<=n;i++){
		for(t1=t2=0,j=1;j<i;j++)if(dp[j][i]>-1&&get(p[i],p[j],p[1])>=0)sa[++t1]=j;
		for(j=i+1;j<=n;j++)if(get(p[i],p[1],p[j])>=0)sb[++t2]=j;
		sort(sa+1,sa+t1+1,cmp);sort(sb+1,sb+t2+1,cmp);
		for(h=t=1,v=-1;t<=t2;t++){
			for(;h<=t1&&get(p[i],p[sa[h]],p[sb[t]])>0;)v=max(v,dp[sa[h++]][i]);
			if(v>-1)dp[i][sb[t]]=max(dp[i][sb[t]],v+1);
		}
	}
	for(i=1;i<=n;i++)for(j=i+1;j<=n;j++)ans=max(ans,dp[i][j]);
	printf("%d",ans);
}

T2 Bankomat 写了个set竟然T了QAQ改成vector又MLE了我曰。。MD本地都秒过的,当过了算了

#include<cstdio>
#include<set>
using namespace std;
int n,i,j,a,b,c,d,k,p,ans;char s[1001000];
set<int>st[1010][10];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)for(scanf("%d%s",&k,s+1),j=1;j<=k;j++)st[i][s[j]-'0'].insert(j);
	for(a=0;a<=9;a++)for(b=0;b<=9;b++)for(c=0;c<=9;c++)for(d=0;d<=9;d++){
		for(i=1;i<=n;i++){
			if(!st[i][a].size()||!st[i][b].size()||!st[i][c].size()||!st[i][d].size())break;
			p=*st[i][a].begin();
			if(*--st[i][b].end()<p)break;p=*st[i][b].lower_bound(p);
			if(*--st[i][c].end()<p)break;p=*st[i][c].lower_bound(p);
			if(*--st[i][d].end()<p)break;p=*st[i][d].lower_bound(p);
		}
		if(i>n)ans++;
	}
	printf("%d",ans);
}
#include<cstdio>
#include<set>
#include<vector>
using namespace std;
int n,i,j,u,a,b,c,d,k,p,ans;char s[1001000];
set<int>st[10];vector<int>to[1010][10];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
		for(j=0;j<=9;j++)st[j].clear(),to[i][j].push_back(0);
		for(scanf("%d%s",&k,s+1),j=1;j<=k;j++)st[s[j]-'0'].insert(j);
		for(j=1;j<=k;j++)
			for(u=0;u<=9;u++)
				if(!st[u].size())to[i][u].push_back(-1);
				else if(*--st[u].end()<j)to[i][u].push_back(-1);
				else to[i][u].push_back(*st[u].lower_bound(j));
	}
	for(a=0;a<=9;a++)for(b=0;b<=9;b++)for(c=0;c<=9;c++)for(d=0;d<=9;d++){
		for(i=1;i<=n;i++){
			p=to[i][a][1];if(p==-1)break;
			p=to[i][b][p];if(p==-1)break;
			p=to[i][c][p];if(p==-1)break;
			p=to[i][d][p];if(p==-1)break;
		}
		if(i>n)ans++;
	}
	printf("%d",ans);
}
#include<cstdio>
#include<set>
#include<vector>
using namespace std;
int n,i,j,u,a,b,c,d,A,B,C,D,k,sum,ans[10][10][10][10];char s[1001000];
set<int>st[10];vector<int>to[10];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
		for(j=0;j<=9;j++)st[j].clear(),to[j].clear();
		for(scanf("%d%s",&k,s+1),j=1;j<=k;j++)st[s[j]-'0'].insert(j);
		for(j=0;j<=k;j++)
			for(u=0;u<=9;u++)
				if(!st[u].size())to[u].push_back(-1);
				else if(*--st[u].end()<j)to[u].push_back(-1);
				else to[u].push_back(*st[u].lower_bound(j));
		for(a=0;a<=9;a++){
			A=to[a][0];
			if(A!=-1)for(b=0;b<=9;b++){
				B=to[b][A];
				if(B!=-1)for(c=0;c<=9;c++){
					C=to[c][B];
					if(C!=-1)for(d=0;d<=9;d++){
						D=to[d][C];if(D==-1)continue;
						ans[a][b][c][d]++;
					}
				}
			}
		}
	}
	for(a=0;a<=9;a++)for(b=0;b<=9;b++)for(c=0;c<=9;c++)for(d=0;d<=9;d++)if(ans[a][b][c][d]==n)sum++;
	printf("%d",sum);
}

T3 Pun-point 找出重心,然后转一圈统计出每个角的叉积和点积,然后做一遍字符串的最小表示,再倒着做一遍,就可以判断点集的同构了,但是小数据全过大数据全是NIE,也不知道为什么

#include<cstdio>
#include<algorithm>
#define N 26000
#define eps 1e-9
using namespace std;typedef double LL;
struct P{LL x,y;}p[N],s[N],w[N],q[N];
int n,m,i,j,k,t,T;LL sx,sy;
LL abs(LL x){return x<0?-x:x;}
LL dis(P a,P b){return (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y);}
P operator-(P a,P b){P t;t.x=a.x-b.x;t.y=a.y-b.y;return t;}
LL operator*(P a,P b){return a.x*b.y-a.y*b.x;}
LL operator/(P a,P b){return a.x*b.x+a.y*b.y;}
bool cmp(P a,P b){
	LL t=(a-p[1])*(b-p[1]);
	if(abs(t)<eps)return dis(a,p[1])<dis(b,p[1]);
	return t<0;
}
int Cmp(P a,P b){
	if((a.x*b.y-b.x*a.y)<eps)return 0;
	if(!a.y)return a.x>0?1:-1;
	if(!b.y)return b.x<0?1:-1;
	return a.x/a.y>b.x/b.y?1:-1;
}
void kmp(int n){
	for(k=0,i=1,j=2;j<=n&&k<=n;){
		t=Cmp(s[(i+k-1)%n+1],s[(j+k-1)%n+1]);
		t?t>0?i=max(j,i+k+1),j=i+1:j=j+k+1,k=0:k++;
	}
}
void get(int n){
	for(sx=sy=0,i=1;i<=n;i++)scanf("%lf%lf",&p[i].x,&p[i].y),sx+=p[i].x,sy+=p[i].y,p[i].x*=n,p[i].y*=n;
	for(i=1;i<=n;i++)p[i].x-=sx,p[i].y-=sy;
	sort(p+2,p+n+1,cmp);
	for(p[0]=p[n],p[n+1]=p[1],i=1;i<=n;i++){
		s[i].x=(p[i]-p[i-1])*(p[i+1]-p[i]);
		s[i].y=(p[i]-p[i-1])/(p[i+1]-p[i]);
	}
	kmp(n);
	for(j=1;j<=n;j++)p[j]=s[(i+j-2)%n+1];
	for(i=1,j=n;i<=j;i++,j--)swap(s[i],s[j]);
	kmp(n);
	for(j=1;j<=n;j++)q[j]=s[(i+j-2)%n+1];
}
int main(){
	scanf("%d",&m);get(m);for(i=1;i<=m;i++)w[i]=p[i];
	for(scanf("%d",&T);T--;){
		scanf("%d",&n);get(n);if(n!=m){puts("NIE");continue;}
		for(i=1;i<=n;i++)if(abs(p[i].x*w[i].y-p[i].y*w[i].x)>eps)break;
		if(i>n){puts("TAK");continue;}
		for(i=1;i<=n;i++)if(abs(w[i].x*q[i].y-w[i].y*q[i].x)>eps)break;
		puts(i>n?"TAK":"NIE");
	}
}

T4 Toy Cars 贪心挺好想的,每次删掉下个出现最末的就行了,实现看起来也挺简单,不过我怎么想也想不到set这题怎么写,而且可并堆也不太资磁,就用了下priority_queue

#include<cstdio>
#include<queue>
#include<map>
#define N 500100
#define mp make_pair
using namespace std;
priority_queue<pair<int,int> >q;
int m,k,n,i,x,ans,la[N],a[N],ne[N];bool is[N];
int main(){
	scanf("%d%d%d",&m,&k,&n);
	for(i=1;i<=m;i++)la[i]=n+1;
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=n;i;i--)ne[i]=la[a[i]],la[a[i]]=i;
	for(i=1;i<=n;i++){
		if(is[a[i]]){q.push(mp(ne[i],a[i]));continue;}
		if(k){
			ans++;q.push(mp(ne[i],a[i]));
			is[a[i]]=1;k--;
		}else{
			while(!is[(q.top()).second])q.pop();
            is[q.top().second]=0;q.pop();
            ans++;q.push(mp(ne[i],a[i]));
            is[a[i]]=1;
		}
	}
	printf("%d",ans);
}

T5 ska Piggy banks 我画了几个图发现不管怎么连只要是一个联通块砸碎一个存钱罐就够了,所以只要判断联通块个数就可以了,一开始写了个没路径压缩的T了,然后改成路径压缩+按秩合并就A了

#include<cstdio>
int n,i,x,p,q,ans,fa[1001000];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)fa[i]=i;
	for(i=1;i<=n;i++){
		scanf("%d",&x);
		p=find(x);q=find(i);
		fa[p]=q;
	}
	for(i=1;i<=n;i++)if(find(i)==i)ans++;
	printf("%d",ans);
}

T7 Bank notes 混合背包问题,直接二进制拆分做背包就行了。。C++真是方便,当初写PAS可是超过1K的

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,i,j,x,w,b[222],dp[20020];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&b[i]);
	memset(dp,0x7f,sizeof(dp));dp[0]=0;
	for(i=1;i<=n;i++){
		scanf("%d",&x);
		for(j=0;x>=(1<<j);j++){
			x-=(1<<j);
			for(w=20000;w>=(1<<j)*b[i];w--)dp[w]=min(dp[w],dp[w-(1<<j)*b[i]]+(1<<j));
		}
		for(w=20000;w>=x*b[i];w--)dp[w]=min(dp[w],dp[w-x*b[i]]+x);
	}
	scanf("%d",&w);printf("%d",dp[w]);
}

T8 Dicing 很简单的二分+网络流,二分每个人赢得局数Mid,源点向每个人连mid,每场比赛建点,两个人向比赛连边,比赛向汇点连边判断是否满流即可 有个不到1K的小哥实在是太厉害了,把我碾了

#include<cstdio>
#include<cstring>
#define N 20020
#define M 80080
#define inf 1e9
int n,m,s,t,u,i,x,y,tot,now,tmp,flow,ans,l,r,mid,fir[N],cur[N],pre[N],num[N],d[N],la[M],va[M],ne[M];
void ins(int x,int y,int z){
	la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;
	la[++tot]=x;va[tot]=0;ne[tot]=fir[y];fir[y]=tot;
}
int ISAP(){
	memset(num,0,sizeof(num));memset(d,0,sizeof(d));memset(pre,0,sizeof(pre));
	for(i=1;i<=t;i++)cur[i]=fir[i];u=s;num[0]=t;flow=0;
	while(d[s]<t){
		if(u==t){
			now=inf;
			for(i=s;i!=t;i=la[cur[i]])if(va[cur[i]]<now)now=va[cur[u=i]];
			for(i=s;i!=t;i=la[cur[i]])va[cur[i]]-=now,va[cur[i]^1]+=now;
			flow+=now;
		}
		for(i=cur[u];i;i=ne[i])if(va[i]&&d[la[i]]+1==d[u])break;
		if(i)cur[u]=i,pre[la[i]]=u,u=la[i];else{
			if(0==--num[d[u]])break;
			for(i=cur[u]=fir[u],tmp=t;i;i=ne[i])if(d[la[i]]<tmp&&va[i])tmp=d[la[i]];
			++num[d[u]=tmp+1];if(u!=s)u=pre[u];
		}
	}
	return flow;
}
int main(){
	scanf("%d%d",&n,&m);s=n+m+1;t=s+1;tot=1;now=1;
	for(i=1;i<=n;i++)ins(s,i,1);
	for(i=1;i<=m;i++)scanf("%d%d",&x,&y),ins(x,i+n,1),ins(y,i+n,1),ins(i+n,t,1);
	for(l=1,r=m;l<=r;){
		mid=l+r>>1;
		memset(va,0,sizeof(va));
		for(i=1;i<=n;i++)va[i*2]=mid,va[i*2+1]=0;
		for(i=1;i<=m*3;i++)va[(i+n)*2]=1,va[(i+n)*2+1]=0;
		if(ISAP()==m)r=mid-1,ans=mid;else l=mid+1;
	}
	printf("%d",ans);
}

T9 A Journey to Mars 用拆成一条链,然后用单调队列维护出最右可以到哪个点就可以得到答案,不过要注意正反要各做一遍

#include<cstdio>
#include<set>
#define N 2001000
using namespace std;typedef long long LL;
int n,i,t,res,r[N],q[N],a[N],b[N],sum[N];bool f[N];
void work(int k){
	for(i=1;i<=n;i++)sum[i]=sum[i-1]+a[i]-b[i];
	for(i=1;i<=n;i++)sum[i+n]=sum[i+n-1]+a[i]-b[i];
	for(t=0,sum[2*n+1]=-1e9,i=0;i<=n*2+1;q[++t]=i++)for(;t&&sum[i]<sum[q[t]];r[q[t--]]=i-1);
	for(i=0;i<n;i++)if(r[i]-i+1>=n)f[k?n-i:i+1]=1;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]),res+=a[i]-b[i];
	if(res<0){for(i=1;i<=n;i++)puts("NIE");return 0;}
	work(0);for(i=1;i<=n/2;i++)swap(a[i],a[n-i+1]),swap(b[i],b[n-i+1]);
	t=b[1];for(i=1;i<n;i++)b[i]=b[i+1];b[n]=t;
	work(1);for(i=1;i<=n;i++)puts(f[i]?"TAK":"NIE");
}

T10 Sum- Fibonacci sums 暴力调整,当f[i]=3时,f[i]=0,f[i+2]++,f[i-2]++;当f[i]=2时,如果f[i-1]=1,f[i]=0,f[i-1]--,f[i+2]++,否则f[i+1]++,f[i-2]++;然后处理进位,可以证明这样调整不会出现反例

#include<cstdio>
#include<algorithm>
using namespace std;
int i,n,m,x,d,f[1001000];
void get(int x){for(;f[x]&&f[x+1];x+=2)f[x]--,f[x+1]--,f[x+2]++;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&f[i]);
	for(scanf("%d",&m),n=max(n,m)+3,i=1;i<=m;i++)scanf("%d",&x),f[i]+=x;
	for(i=n;i;i--)if(f[i]==3)f[i]=0,f[i+2]++,get(i+2),f[i-2]++,get(i-3);
	else if(f[i]==2){
		if(f[i-1])f[i]=0,f[i-1]--,f[i+2]++,get(i+2);
		else f[i]=0,f[i+1]++,get(i+1),f[i-2]++,get(i-3);
	}else get(i-1);
	for(f[1]+=f[0];n&&!f[n];n--);
	for(printf("%d",n),i=1;i<=n;i++)printf(" %d",f[i]);
}

T11 Template 好神啊,二分+KMP。。

#include<cstdio>
#include<cstring>
#define N 500500
int n,i,k,l,r,mid,cnt,ans,a[N],p[N];char s[N];
bool ok(int x){
	int k=p[x];
	for(int i=x+1;i<=n;i++){
		for(;k&&s[k+1]^s[i];k=p[k]);
		if(s[k+1]==s[i])k++;
		if(!k)return 0;
		if(k==x)k=p[k];
	}
	return 1;
}
int main(){
	scanf("%s",s+1);n=strlen(s+1);
	for(i=2;i<=n;i++){
		for(;k&&s[k+1]^s[i];k=p[k]);
		if(s[k+1]==s[i])k++;
		p[i]=k;
	}
	for(k=n;k;k=p[k])a[++cnt]=k;
	for(l=1,r=cnt;l<=r;){
		mid=l+r>>1;
		if(ok(a[mid]))l=mid+1,ans=mid;else r=mid-1;
	}
	printf("%d",a[ans]);
} 

T12 Akc- Special Forces Manoeuvres 两个圆的并的右端一定是一个点,那么求多个圆的并就可以先算出每两个圆两两相交的右端点的最左边的点,把这个点带进去看看是否在所有圆内就可以了

#include<cstdio>
#include<cmath>
#define N 2022
#define eps 1e-8
#define sqr(x)((x)*(x))
#define dis(a,b)(sqr(x[a]-x[b])+sqr(y[a]-y[b]))
#define up if(mx>ix)mx=ix,my=iy;
#define in(i,vx,vy)(sqr(vx-x[i])+sqr(vy-y[i])<sqr(r[i])+eps)
int n,i,j;double mx,my,ix,iy,jx,jy,ux,uy,s,t,l,x[N],y[N],r[N];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
		scanf("%lf%lf%lf",&x[i],&y[i],&r[i]);
		if(i==1)mx=x[i]+r[i],my=y[i];
		for(j=1;j<i;j++){
			l=dis(i,j);
			ix=x[i]+r[i];iy=y[i];if(in(j,ix,iy)){up continue;}
			ix=x[j]+r[j];iy=y[j];if(in(i,ix,iy)){up continue;}
			if(l<sqr(r[i]+r[j])+eps){
				s=((r[i]-r[j])*(r[i]+r[j])/l+1)/2;
				t=sqrt(-(l-sqr(r[i]+r[j]))*(l-sqr(r[i]-r[j]))/(l*l*4));
				ux=x[j]-x[i],uy=y[j]-y[i];
				ix=x[i]+s*ux+t*uy;iy=y[i]+s*uy-t*ux;
				jx=x[i]+s*ux-t*uy;jy=y[i]+s*uy+t*ux;
				if(ix<jx)ix=jx,iy=jy;
				up
			}else return printf("%d\n",i),0;
		}
		for(j=1;j<=i;j++)if(!in(j,mx,my))return printf("%d\n",i),0;
	}
	puts("NIE");
}

T13 The Bus 直接按照x端点第一关键字y端点第二关键字然后树状数组优化DP就行了

#include<cstdio>
#include<algorithm>
#define N 100100
using namespace std;
int n,m,k,i,j,t,va,ans,tol,cnt1,cnt2,v1[N],v2[N],c[N],f[N];
struct EG{int x,y,z;}eg[N];bool cmp(EG a,EG b){return a.x<b.x||a.x==b.x&&a.y<b.y;}
void ins(int x,int z){for(;x<=cnt2;x+=x&-x)c[x]=max(c[x],z);}
int que(int x){for(ans=0;x;x-=x&-x)ans=max(ans,c[x]);return ans;}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(i=1;i<=k;i++)scanf("%d%d%d",&eg[i].x,&eg[i].y,&eg[i].z),v1[i]=eg[i].x,v2[i]=eg[i].y;
	sort(v1+1,v1+k+1);sort(v2+1,v2+k+1);cnt1=unique(v1+1,v1+k+1)-(v1+1);cnt2=unique(v2+1,v2+k+1)-(v2+1);
	for(i=1;i<=k;i++)eg[i].x=lower_bound(v1+1,v1+cnt1+1,eg[i].x)-v1,eg[i].y=lower_bound(v2+1,v2+cnt2+1,eg[i].y)-v2;
	sort(eg+1,eg+k+1,cmp);t=1;
	for(i=1;i<=k;i++)f[i]=que(eg[i].y)+eg[i].z,ins(eg[i].y,f[i]),tol=max(tol,f[i]);
	printf("%d\n",tol);
}

T15 Dwu-Double-row 考虑到只有处于一个连通块能相互影响,就考虑每个连通块的情况

枚举连通块中的一个点是否改变,那么剩下的是否改变就可以得出,取一个改变代价小的即可

#include<cstdio>
#include<algorithm>
#define N 101000
using namespace std;
int n,i,j,k,x,p,q,ans,a[N],b[N],fir[N],sec[N],v[N];
int get(int x,int z){if(!fir[x])fir[x]=z;else sec[x]=z;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]),get(a[i],i);
	for(i=1;i<=n;i++)scanf("%d",&b[i]),get(b[i],i);
	for(i=1;i<=n;i++)if(!v[i]){
		v[i]=1;if(a[i]==b[i])continue;
		for(p=0,q=1,x=a[j=i];;v[j=k]=1){
			k=fir[x];if(k==j)k=sec[x];
			if(!k||k==i)break;
			if(x==b[k])q++,x=a[k];else p++,x=b[k];
		}
		if(!k)for(x=b[j=i];;v[j=k]=1){
			k=fir[x];if(k==j)k=sec[x];
			if(!k||k==i)break;
			if(x==b[k])p++,x=a[k];else q++,x=b[k];
		}
		ans+=min(p,q);
	}
	printf("%d",ans);
}

这届也感觉切不动了TAT,换一届简单点的切吧

POI2012(17/17)(1426/1426)

T1 Festival 首先差分构图,然后可以进行缩点,这样不同强连通分量互不影响,在每个强连通分量中求出最大的绝对值+1即为该强连通分量的贡献,如果有正环则无解

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 610
#define M 200020
#define inf -0x3f3f3f3f
using namespace std;
int n,m1,m2,i,j,x,y,tot,top,sz,cnt,now,ans,z[N],d[N][N],fir[N],st[N],low[N],dfn[N],bl[N],la[M],va[M],ne[M];bool is[N];
void ins(int x,int y,int z){la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;}
bool cmp(int x,int y){return bl[x]<bl[y];}
void tarjan(int x){
	low[x]=dfn[x]=++sz;is[x]=1;st[++top]=x;int y,i;
	for(i=fir[x];i;i=ne[i])if(!dfn[y=la[i]])tarjan(y),low[x]=min(low[x],low[y]);else if(is[y])low[x]=min(low[x],dfn[y]);
	if(dfn[x]==low[x])for(cnt++,now=0;now!=x;now=st[top--],bl[now]=cnt,is[now]=0);
}
int main(){
	for(scanf("%d%d%d",&n,&m1,&m2);m1--;)scanf("%d%d",&x,&y),ins(x,y,1),ins(y,x,-1);
	for(;m2--;)scanf("%d%d",&x,&y),ins(x,y,0);memset(d,inf,sizeof(d));
	for(i=1;i<=n;d[i][i]=0,z[i]=i++)if(!dfn[i])tarjan(i);
	for(x=1;x<=n;x++)for(i=fir[x];i;i=ne[i])if(bl[x]==bl[la[i]])d[x][la[i]]=max(d[x][la[i]],va[i]);
	for(x=1;x<=n;x++)for(i=1;i<=n;i++)if(d[i][x]!=inf)for(j=1;j<=n;j++)if(d[x][j]!=inf)d[i][j]=max(d[i][j],d[i][x]+d[x][j]);
	for(i=1;i<=n;i++)if(d[i][i]>0)return puts("NIE"),0;sort(z+1,z+n+1,cmp);
	for(x=1;x<=n;ans+=now+1,x=y){
		for(y=x;y<=n&&bl[z[x]]==bl[z[y]];y++);
		for(now=0,i=x;i<y;i++)for(j=x;j<y;j++)now=max(now,abs(d[z[i]][z[j]]));
	}
	printf("%d",ans);
}

T2 Letters 首先一个想法是相同字母的相对顺序不会改变,于是可以得知目标串每个字母在原串中的位置,然后答案就是这个的逆序对

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n,i,x,sum,now,c[1001000];
queue<int>q[26];char s[1001000];long long ans;
void add(int x){for(;x<=n;x+=x&-x)c[x]++;}
long long que(int x){for(now=0;x;x-=x&-x)now+=c[x];return now;}
int main(){
	for(scanf("%d%s",&n,s+1),i=1;i<=n;i++)q[s[i]-'A'].push(i);
	for(scanf("%s",s+1),i=1;i<=n;i++){
		x=q[s[i]-'A'].front();q[s[i]-'A'].pop();
		add(n-x+1);ans+=que(n-x);
	}printf("%lld",ans);
}

T3 Distance 先O(n)线性筛扫出每个数的因数个数,然后要求每个数的min(d[i]+d[j]-2*d[gcd(i,j)]),枚举这个因数,再加上是这个因数倍数的b[i]的最小值即可,由于可能这个最小值就是该数,还要统计一个次小值,时间复杂度O(n^1.5)

#include<cstdio>
#include<cstring>
#define N 1001000
int n,i,j,x,w,t,u,d1[N],d2[N],e1[N],e2[N],b[N],p[N],a[N];bool f[N];
void cg(int x,int y,int z){if(z<d1[x]||z==d1[x]&&y<e1[x])d2[x]=d1[x],e2[x]=e1[x],d1[x]=z,e1[x]=y;else if(y!=e1[x]&&(z<d2[x]||z==d2[x]&&y<e2[x]))d2[x]=z,e2[x]=y;}
void get(int z,int y){if(z<w||z==w&&y<u)w=z,u=y;}
void r(int j){get(e1[j]==i?d2[j]-2*b[j]:d1[j]-2*b[j],e1[j]==i?e2[j]:e1[j]);}
int main(){
	for(memset(d1,63,sizeof(d1)),memset(d2,63,sizeof(d2)),i=2;i<=1000000;i++){
        if(!f[i])p[++t]=i,b[i]=1;
        for(j=1;j<=t&&i*p[j]<=1000000;j++){
            f[i*p[j]]=1;b[i*p[j]]=b[i]+1;
            if(i%p[j]==0)break;
        }
 	}
	for(scanf("%d",&n),i=1;i<=n;i++){
		scanf("%d",&a[i]);
		for(j=1;j*j<=a[i];j++)if(a[i]%j==0){
			cg(j,i,b[a[i]]);
			if(a[i]*a[i]!=j)cg(a[i]/j,i,b[a[i]]);
		}
	}
	for(i=1;i<=n;printf("%d\n",u),i++)for(w=1e9,j=1;j*j<=a[i];j++)if(a[i]%j==0)r(j),r(a[i]/j);
}

T4 Rendezvous 基环树上求LCA,如果在一棵外向树上直接求,否则判断走环两边的距离,取更优的作为答案

#include<cstdio>
#include<algorithm>
#define N 500500
using namespace std;
int n,Q,i,j,top,tm,tmp,x,y,t,x1,y1,x2,y2,to[N],pos[N],rt[N],v[N],bl[N],sz[N],h[N],fa[N][20];
void fcur(int x){
	v[x]=tm;int y;
	if(v[to[x]]==tm){
		for(y=x,tmp=0;!tmp||x!=y;y=to[y],tmp++)bl[y]=tm,pos[y]=tmp,rt[y]=y;
		sz[tm]=tmp;return;
	}
	if(!v[to[x]])fcur(to[x]);
	if(!bl[x])fa[x][0]=to[x],h[x]=h[to[x]]+1,rt[x]=rt[to[x]];
}
int lca(int x,int y){
	if(h[x]<h[y])swap(x,y);
	int t=h[x]-h[y],i;
	for(i=0;i<=19;i++)if(t&(1<<i))x=fa[x][i];
	for(i=19;i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
	return x==y?x:fa[x][0];
}
int dis(int x,int y,int p){return (y-x+p)%p;}
int main(){
	for(scanf("%d%d",&n,&Q),i=1;i<=n;i++)scanf("%d",&to[i]);
	for(i=1;i<=n;i++)if(!v[i])tm++,fcur(i);
	for(j=1;j<=19;j++)for(i=1;i<=n;i++)fa[i][j]=fa[fa[i][j-1]][j-1];
	for(;Q--;){
		scanf("%d%d",&x,&y);
		if(bl[rt[x]]!=bl[rt[y]]){puts("-1 -1");continue;}
		if(rt[x]==rt[y]){
			t=lca(x,y);
			printf("%d %d\n",h[x]-h[t],h[y]-h[t]);
			continue;
		}
		x1=h[x]+dis(pos[rt[x]],pos[rt[y]],sz[bl[rt[x]]]);y1=h[y];
		x2=h[x];y2=h[y]+dis(pos[rt[y]],pos[rt[x]],sz[bl[rt[y]]]);
		if(max(x1,y1)<max(x2,y2))printf("%d %d\n",x1,y1);else
		if(max(x2,y2)<max(x1,y2))printf("%d %d\n",x2,y2);else
		if(min(x1,y1)<min(x2,y2))printf("%d %d\n",x1,y1);else
		if(min(x2,y2)<min(x1,y1))printf("%d %d\n",x2,y2);else
		printf("%d %d\n",max(x1,y1),min(x1,y1));
	}
}

T5 Well 首先很容易想到二分,难点在于如何快速判断是否合法

可以先从左往右扫一遍,再从右往左扫一遍,把差距调到mid以内

然后枚举每个位置变为0,则他对答案的影响是左边和右边各一个等差数列,因为是单调的,可以顺序扫一遍得出每个位置改为0的代价

这样总的时间复杂度是O(nlgn)的

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;typedef long long LL;
int n,i,j,l,r,mid,t,z,k,a[N],b[N];LL m,now,s[N],f[N];
int ok(int w){
	for(b[1]=a[1],i=2;i<=n;i++)b[i]=min(a[i],b[i-1]+w);
	for(i=n-1;i;i--)b[i]=min(b[i],b[i+1]+w);
	for(now=0,i=1;i<=n;i++)now+=a[i]-b[i],s[i]=s[i-1]+b[i];
	for(i=j=1;i<=n;i++){
		for(;j<i&&b[j]<=(LL)(i-j)*w;j++);
		f[i]=s[i-1]-s[j-1]-(LL)(1+i-j)*(i-j)*w/2;
	}
	for(i=j=n;i;i--){
		for(;j>i&&b[j]<=(LL)(j-i)*w;j--);
		f[i]+=s[j]-s[i]-(LL)(1+j-i)*(j-i)*w/2;
	}
  for(i=1;i<=n;i++)if(f[i]+b[i]+now<=m)return i;return 0;
}
int main(){
	for(scanf("%d%lld",&n,&m),i=1;i<=n;i++)scanf("%d",&a[i]),r=r>a[i]?r:a[i];
	while(l<=r)if(t=ok(mid=(l+r)>>1))r=(z=mid)-1,k=t;else l=mid+1;
	printf("%d %d",k,z);
}

T6 Vouchers 因为知道这是一道傻逼题,就往傻逼的地方想,觉得暴力取应该没什么问题,是一个调和级数的复杂度,但由于姿势不太对WA了好几发

#include<cstdio>
#define N 1001000
int m,i,n,x,p,t,now,q[N],u[N];bool v[N],s[N];
long long a[N],top;
int main(){
	for(scanf("%d",&m),i=1;i<=m;i++)scanf("%d",&x),s[x]=1;
	for(scanf("%d",&n),i=1;i<=n;i++){
		scanf("%d",&x);
		for(now=0,p=u[x]+x;p<=1000000&&now<x;p+=x)if(!v[p]){
			++top;v[p]=1;now++;
			if(s[p])a[++t]=top;
		}
		top+=x-now;u[x]=p-x;
	}
	for(printf("%d\n",t),i=1;i<=t;i++)printf("%lld\n",a[i]);
}

T7 Cloakroom 凑来凑去感觉只有O(nk)才有些许希望,于是可以把每个物品和询问按左端点排序,然后当物品左端点小于等于询问左端点时进行一次背包,dp的值表示和为这个价值时右端点较小值中的最大值,这样的时间复杂度是O(nk+qlgq+nlgn)的,能卡过去

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1010
using namespace std;
int n,i,Q,t,j,ans[N*N],dp[N*N];
struct E{int c,l,r;}e[N];struct U{int l,k,r,z;}q[N*N];
bool cmp(E a,E b){return a.l<b.l;}bool Cmp(U a,U b){return a.l<b.l;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d%d",&e[i].c,&e[i].l,&e[i].r);sort(e+1,e+n+1,cmp);
	for(scanf("%d",&Q),i=1;i<=Q;i++)scanf("%d%d%d",&q[i].l,&q[i].k,&q[i].r),q[i].r+=q[i].l+1,q[i].z=i;
	sort(q+1,q+Q+1,Cmp);memset(dp,-1,sizeof(dp));dp[0]=2e9;
	for(t=i=1;i<=Q;i++){
		for(;e[t].l<=q[i].l&&t<=n;t++)
			for(j=100001;j>=e[t].c;j--)dp[j]=max(dp[j],min(dp[j-e[t].c],e[t].r));
		ans[q[i].z]=(dp[q[i].k]>=q[i].r);
	}
	for(i=1;i<=Q;i++)puts(ans[i]?"TAK":"NIE");
}

T8 A Horrible Poem 首先可以用哈希O(1)判断长度x是否是l-r的循环节,只要判l~(r-x)和(l+x)~r是否相等就行了,但如果暴力判时间复杂度是n^1.5会T,可以先枚举出所有质数,然后如果x是l~r的循环节,那么最小循环节一定是x的约数,于是可以枚举所有质因子,不断除去就可以在O(nlgn)时间内出解

#include<cstdio>
#define S 1000000007
#define N 500500
typedef unsigned long long LL;
int n,Q,i,j,p,len,x,y,z,to[N],prime[N];char s[N];LL a[N],pow[N];
bool ck(int l1,int r1,int l2,int r2){return a[r1]-a[l1-1]*pow[r1-l1+1]==a[r2]-a[l2-1]*pow[r2-l2+1];}
int main(){
	for(scanf("%d%s%d",&n,s+1,&Q),pow[0]=1,i=1;i<=n;i++)pow[i]=pow[i-1]*S,a[i]=a[i-1]*S+(s[i]-'a'+1);
	for(i=2;i<=n;i++){
		if(!to[i])to[i]=prime[++p]=i;
		for(j=1;j<=p&&i*prime[j]<=n;j++){
			to[i*prime[j]]=prime[j];
			if(i%prime[j]==0)break;
		}
	}
	for(;Q--;printf("%d\n",len)){
		scanf("%d%d",&x,&y);len=y-x+1;
		for(i=len;i>1;){
			for(z=to[i];len%z==0&&ck(x,y-len/z,x+len/z,y);len/=z);
			for(;i%z==0;i/=z);
		}
	}
}

T9 Fibonacci Representation 一开始写了个暴力set T了,然后加了个map+lower_bound才A。。

#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;typedef long long LL;
int i,Q;
LL x,f[99];map<LL,int>F;
int find(LL n){
	if(F[n])return F[n];
	int b=lower_bound(f,f+91,n)-f;
	if(f[b]==n)return 1;
	return F[n]=min(find(n-f[b-1]),find(f[b]-n))+1;
}
int main(){
	f[1]=1;f[2]=2;for(i=3;i<=91;i++)f[i]=f[i-1]+f[i-2];
	for(scanf("%d",&Q);Q--;)scanf("%lld",&x),printf("%d\n",find(x));
}

T10 Squarks 首先最小的数一定是x[1]+x[2],次小的数一定是x[1]+x[3],而x[2]+x[3]可能是第3-n小的数,枚举x[2]+x[3]的位置,然后解方程得到x[1]、x[2]和x[3],然后剩下的数最小的一定是x[1]+x[4],由于x[1]已知,于是x[4]可知,然后判断x[2]+x[4]、x[3]+x[4]是否出现并删除,然后x[5]..x[n]以此类推,最后再判断一下是否是顺序上升的就可以了,时间复杂度O(n^3)

#include<cstdio>
#include<set>
#include<algorithm>
#define N 310
using namespace std;
int n,m,i,j,tot,a[N*N],x[N],ans[N][N];
multiset<int>w,s;
void ck(int a3){
	if(a[1]+a[2]+a3&1)return;int i,j;s=w;
	x[1]=a[1]+a[2]-a3>>1;x[2]=a[1]+a3-a[2]>>1;x[3]=a[2]+a3-a[1]>>1;
	s.erase(s.find(a[1]));s.erase(s.find(a[2]));s.erase(s.find(a3));
	for(i=4;i<=n;i++){
		x[i]=*s.begin()-x[1];
		if(x[i]<0)return;
		for(j=1;j<i;s.erase(s.find(x[j]+x[i])),j++)if(s.find(x[j]+x[i])==s.end())return;
	}
	for(i=1;i<n;i++)if(x[i]>=x[i+1])return;
	for(tot++,i=1;i<=n;i++)ans[tot][i]=x[i];
}
int main(){
	for(scanf("%d",&n),m=n*(n-1)/2,i=1;i<=m;i++)scanf("%d",&a[i]),w.insert(a[i]);sort(a+1,a+m+1);
	for(i=3;i<=n;i++)if(i==3||a[i]!=a[i-1])ck(a[i]);
	for(printf("%d\n",tot),i=1;i<=tot;puts(""),i++)for(j=1;j<=n;j++)printf("%d ",ans[i][j]);
}

T11 Bidding 面向数据编程

#include<cstdio>
int main(){printf("756396726\n1");}

T12 Salaries 算出每个点的取值范围,从小到大扫描,如果当前maxi=k只有一个点且在1-k中只有这一个数可用则这个点值确定,如果maxi=k的节点数=1-k中可用节点数把这些节点全部删去,标记为不可用

#include<cstdio>
#define N 1001000
int n,i,h,t,tot,res,rt,S,fir[N],ne[N],la[N],p[N],z[N],fa[N],s[N],sm[N],q[N],ans[N];bool v[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)fa[i]=i;
	for(i=1;i<=n;i++){
		scanf("%d%d",&p[i],&z[i]);
		if(z[i])v[z[i]]=1,ans[i]=z[i],fa[z[i]]=z[i]-1;
		if(p[i]!=i)ins(p[i],i);else p[rt=i]=0;
	}
	for(z[0]=n+1,q[h=t=1]=rt;h<=t;h++){
		if(!z[q[h]])z[q[h]]=gf(z[p[q[h]]]-1),s[z[q[h]]]=q[h],sm[z[q[h]]]++;
		for(i=fir[q[h]];i;i=ne[i])q[++t]=la[i];
	}
	for(i=1;i<=n;i++){
		if(!v[i])S=i,res++;
		if(sm[i]&&res==1)ans[s[i]]=S;
		if(sm[i])res-=sm[i],S=0;
	}
	for(i=1;i<=n;i++)printf("%d\n",ans[i]);
}

T13 Leveling Ground 用前缀和使其变成单点修改,可以求出ax+by=c使得代价最小,先让x为正,y为负,然后x不断减b,y加a,通过堆得到一个最优的答案。真的好神啊~

#include<cstdio>
#include<algorithm>
#define N 100100
using namespace std;typedef long long LL;
int n,i,rt,s[N][2];
LL a,b,t,x,y,la,ans,tot,c[N],p[N],q[N],v[N];
LL exgcd(LL a,LL b,LL&x,LL&y){
	if(!b){x=1;y=0;return a;}
	LL t=exgcd(b,a%b,y,x);y-=x*(a/b);
	return t;
}
LL get(int x){
	LL now=0;
	if(p[x]>0)now+=p[x];if(q[x]>0)now+=q[x];
	if(q[x]+a>0)now-=q[x]+a;return now;
}
int mg(int x,int y){
	if(!x||!y)return x+y;
	if(v[x]<v[y])swap(x,y);
	s[x][1]=mg(s[x][1],y);
	swap(s[x][0],s[x][1]);
	return x;
}
int main(){
	scanf("%d%lld%lld",&n,&a,&b);if(b<a)swap(a,b);
	t=exgcd(a,b,x,y);a/=t;b/=t;x=(x+b)%b;
	for(i=1;i<=n;i++)scanf("%lld",&y),c[i]=la-y,la=y;
	for(i=1,c[++n]=la;i<=n;i++){
		if(c[i]%t)return puts("-1"),0;c[i]/=t;
		p[i]=(c[i]*x)%b;if(p[i]<0)p[i]+=b;
		q[i]=(c[i]-p[i]*a)/b;tot+=p[i];
		v[i]=get(i);
		rt=mg(rt,i);
	}
	for(tot/=b;tot--;){
		x=rt;rt=mg(s[rt][0],s[rt][1]);s[x][0]=s[x][1]=0;
		p[x]-=b;q[x]+=a;v[x]=get(x);
		rt=mg(rt,x);
	}
	for(i=1;i<=n;i++){if(p[i]>0)ans+=p[i];if(q[i]>0)ans+=q[i];}
	printf("%lld",ans);
}

T14 Minimalist Security 对于每一个连通块,对一个点设未知数x,则剩下的点都可以用x表示,如果碰到两边都已经表示过,如果不同号则判一下常数是否合法,否则则可以算出x的值,如果x的值有多个则不合法,顺便可以根据0<=x+w[i]<=p[i]算出x的[l,r]取值范围,然后就可以得到答案了

#include<cstdio>
#include<algorithm>
#define N 500500
#define M 6006000
using namespace std;
int n,m,i,x,y,z,h,t,u,tot,ans,l,r,sz,s,fir[N],la[M],va[M],ne[M],v[N],w[N],p[N],q[N];
long long sum,now,lv,rv;bool flag;
void ins(int x,int y,int z){la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;}
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=n;i++)scanf("%d",&p[i]),sum+=p[i];
	for(;m--;)scanf("%d%d%d",&x,&y,&z),ins(x,y,z),ins(y,x,z);
	for(s=1;s<=n;s++)if(!v[s]){
		for(q[t=1]=s,v[s]=1,ans=-1,h=sz=0,now=0,l=0,r=p[s];h^t;){
			if(v[u=q[++h]]<2)sz++,now+=w[u],l=max(l,-w[u]),r=min(r,p[u]-w[u]);
			else sz--,now+=w[u],l=max(l,w[u]-p[u]),r=min(r,w[u]);
			for(i=fir[u];i;i=ne[i]){
				y=la[i];z=va[i]-w[u];
				if(v[y]){
					if(3-v[u]==v[y]){if(z!=w[y])return puts("NIE"),0;}else{
						x=w[y]-z;if(x&1)return puts("NIE"),0;
						x>>=1;if(v[y]<2)x=-x;
						if(ans<0||ans==x)ans=x;else return puts("NIE"),0;
					}
				}else v[y]=3-v[u],w[y]=z,q[++t]=y;
			}
		}
		if(l>r)return puts("NIE"),0;
		if(ans>0){
			if(ans<l||ans>r)return puts("NIE"),0;
			l=r=ans;
		}
		if(sz>0)lv+=1ll*r*sz+now,rv+=1ll*l*sz+now;else lv+=1ll*l*sz+now,rv+=1ll*r*sz+now;
	}
	printf("%lld %lld",sum-lv,sum-rv);
}

T15 Warehouse Store 一开始写线段树乱搞但发现是不对的,应该要用贪心的思想,每次能加就加,不能加就在已经加的中找最大的换,这样就能保证正确性

#include<cstdio>
#include<map>
#include<queue>
#define mp make_pair
#define N 252000
using namespace std;
priority_queue<pair<int,int> >q;
int n,i,x,va,id,cnt,a[N];bool w[N];long long now;
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<=n;i++){
		scanf("%d",&x);now+=a[i];
		if(now>=(long long)x)now-=x,q.push(mp(x,i)),w[i]=1;else
		if(!q.empty()){
			va=q.top().first;id=q.top().second;
			if(va>x)q.pop(),w[id]=0,now+=va-x,q.push(mp(x,i)),w[i]=1;
		}
	}for(i=1;i<=n;i++)if(w[i])cnt++;
	for(printf("%d\n",cnt),i=1;i<=n;i++)if(w[i])printf("%d\n",i);
}

T16 Prefixuffix 用fi表示串[i+1,n-i]中最长的串使[i+1,i+fi]=[n-i-fi+1,n-i],这时有一个性质f(i-1)<=fi+2,然后就可以线性递推

不过这题unsigned long long自然溢出会挂,于是只能取模,不过这题其实O(nlgn)暴力也能过的、YY大爷%%%

#include<cstdio>
#include<cstring>
#include<algorithm>
#define S 1000000003
#define M 1000000009
#define N 1001000
using namespace std;typedef long long LL;
int n,i,j,ans;char s[N];LL pw[N],sum[N];
LL get(int l,int r){return (sum[r]+M-sum[l-1]*pw[r-l+1]%M)%M;}
int main(){
	for(scanf("%d%s",&n,s+1),pw[0]=i=1;i<=n;i++)pw[i]=pw[i-1]*S%M,sum[i]=(sum[i-1]*S%M+s[i])%M;
	for(i=n>>1;~i;i--)
		for(j=min(n/2-i,j+2);j>=0;j--)if(get(i+1,i+j)==get(n-i-j+1,n-i)){
			if(get(1,i)==get(n-i+1,n))ans=max(ans,i+j);
			break;
		}
	printf("%d",ans);
}

T17 Tour de Byteotia 把大于k的先加入并查集,然后剩余的判是否构成环就可以了,写了个按秩合并的T了,按秩合并+路径压缩也T了,改成路径压缩才A。。

#include<cstdio>
#define N 1001000
int n,m,k,i,p,q,ans,a[N*2],b[N*2],fa[N];char ch;
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
void read(int &x){
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	for(x=0;ch>='0';ch=getchar())x=x*10+ch-'0';
}
int main(){
	for(read(n),read(m),read(k),i=1;i<=n;i++)fa[i]=i;
	for(i=1;i<=m;i++){
		read(a[i]);read(b[i]);
		if(a[i]>k&&b[i]>k)p=gf(a[i]),q=gf(b[i]),fa[p]=q;
	}
	for(i=1;i<=m;i++)if(a[i]<=k||b[i]<=k){
		p=gf(a[i]);q=gf(b[i]);
		if(p==q)ans++;else fa[p]=q;
	}
	printf("%d",ans);
}

POI2008(14+1/16)(3344/3431)

T1 砖块 虽然是一道傻逼题,但是看别人代码都这么长,我也就傻逼地写了个平衡树,很久没写权值平衡树的我竟然1A了,爽~看来平衡树还真是不容易写错啊

#include<cstdio>
#include<algorithm>
#define N 100100
#define inf 1e9
using namespace std;typedef long long LL;
int n,k,i,rt,x,y,tot,u,a[N],sz[N],fa[N],c[N][2],val[N];LL ans,sum[N];
void ps(int k){int l=c[k][0],r=c[k][1];sum[k]=(LL)val[k]+sum[l]+sum[r];sz[k]=sz[l]+sz[r]+1;}
void R(int x){
    int y=fa[x],k=(c[y][0]==x);
    c[y][!k]=c[x][k];fa[c[x][k]]=y;
    fa[x]=fa[y];c[fa[y]][c[fa[y]][1]==y]=x;
    c[x][k]=y;fa[y]=x;ps(y);
}
void sy(int x,int g){for(;(y=fa[x])!=g;R(x))if(fa[y]!=g)R((x==c[y][0])==(y==c[fa[y]][0])?y:x);ps(x);if(!g)rt=x;}
void nn(int &x,int la,int va){x=++tot;fa[x]=la;val[x]=sum[x]=va;sz[x]=1;c[x][0]=c[x][1]=0;}
void ins(int va){
	for(x=rt;c[x][va>val[x]];x=c[x][va>val[x]]);
	nn(c[x][va>val[x]],x,va);sy(c[x][va>val[x]],0);
}
void del(int va){
	for(x=rt;val[x]!=va;x=c[x][va>val[x]]);
	sy(x,0);for(u=c[rt][1];c[u][0];u=c[u][0]);
	sy(u,rt);c[u][0]=c[rt][0];fa[c[rt][0]]=u;fa[u]=0;rt=u;
}
LL find(int k){
	for(x=rt;sz[c[x][0]]!=k;)if(k<=sz[c[x][0]])x=c[x][0];else k-=sz[c[x][0]]+1,x=c[x][1];
	sy(x,0);return sum[c[x][1]]+(LL)val[x]*(sz[c[x][0]]-1)-sum[c[x][0]]-(LL)val[x]*(sz[c[x][1]]-1)-2ll*inf;
}
int main(){
	for(scanf("%d%d",&n,&k),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(ins(inf),ins(-inf),ans=1e17,i=1;i<=n;i++){
		ins(a[i]);if(i>k)del(a[i-k]);
		if(i>=k)ans=min(ans,find((k+1)/2));
	}printf("%lld",ans);
}

T2 海报 首先宽度可以忽略,要想省下矩形,肯定要有相同高度的,可以用一个单调栈维护一个单调下降的高度,每次把要加的高度加入队尾,如果和队尾不同则ans++

#include<cstdio>
int n,x,t,ans,q[250010];
int main(){
	for(scanf("%d",&n);n--;){
		scanf("%d%d",&x,&x);
		for(;x<q[t];t--);
		if(x>q[t])ans++,q[++t]=x;
	}printf("%d",ans);
}

T4 CLO 网络流加了个读入优化还是没卡过去QAQ

只好写了个并查集,大概就是一个联通块如果有>=n条边就是有解的,两个联通块合并时如果任意一个联通块有解则合并的联通块有解,这样就可以再O(n)的时间内出解了。。不过话说回来这题n才100000,为什么网络流跑不过去呢?

#include<cstdio>
#include<cstring>
#define N 333333
#define M 1666666
int n,m,s,t,tot,i,u,flow,now,tmp,x,y,ans,fir[N],cur[N],pre[N],d[N],num[N],la[M],va[M],ne[M];bool v[N];char ch;
void read(int &x){
    for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
    for(x=0;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
}
void ins(int x,int y){
    la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;va[tot]=1;
    la[++tot]=x;ne[tot]=fir[y];fir[y]=tot;va[tot]=0;
}
bool dfs(int x){
    v[x]=1;if(x==t)return 1;
    for(int i=fir[x];i;i=ne[i])
        if(va[i]&&!v[la[i]]){
            pre[la[i]]=i;
            if(dfs(la[i]))return 1;
        }
    return 0;
}
int main(){
	for(read(n),read(m),s=n+m+1,t=s+1,tot=i=1;i<=n;i++)ins(s,i);
	for(i=1;i<=m;i++)read(x),read(y),ins(x,i+n),ins(y,i+n),ins(i+n,t);
	for(;dfs(s);ans++){
        for(i=t;i!=s;i=la[pre[i]^1])va[pre[i]]^=1,va[pre[i]^1]^=1;
        memset(v,0,sizeof(v));
    }
	puts(ans==n?"TAK":"NIE");
}
#include<cstdio>
#define N 100100
int n,m,i,x,y,p,q,fa[N];bool ok[N];
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=n;i++)fa[i]=i;
	for(;m--;){
		scanf("%d%d",&x,&y),p=gf(x),q=gf(y);
		if(p==q)ok[p]=1;else fa[p]=q,ok[q]|=ok[p];
	}
	for(i=1;i<=n;i++)if(!ok[gf(i)])return puts("NIE"),0;
	puts("TAK");
}

T5 激光发射器 直接输出n/2就行啦

#include<cstdio>
int main(){int n;scanf("%d",&n);printf("%d",n/2);}

T6 账本BBB 首先可以O(n)预处理出从每个点开始转一圈会出现的最小前缀和,并求出最少要变得步数,然后枚举每个点作为起点,如果要把减变加就在前面加,对答案有相应贡献,把加变减在后面减,对答案无影响,如果此时最小前缀和仍小于0,就还要变2*((1+x)>>1)次,于是O(n)就可以出解

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define N 2002000
using namespace std;typedef long long LL;
int n,p,q,x,y,i,h,t,wil,Q[N],b[N],sum[N];char s[N];LL ans,cost;
int main(){
	for(scanf("%d%d%d%d%d%s",&n,&p,&q,&x,&y,s+1),i=n<<1;i>n;i--)sum[i]=sum[i+1]+(s[i-n]=='-'?-1:1);
	for(i=n;i;i--)sum[i]=sum[i+1]+(s[i]=='-'?-1:1);
	for(h=1,t=0,i=n<<1;i;i--){
		for(;h<=t&&sum[Q[t]]<sum[i];t--);
		for(Q[++t]=i;Q[h]-i>n&&h<=t;h++);
		if(i<=n)b[i]=sum[i]-sum[Q[h]];
	}
	wil=(q-p-sum[n+1])>>1;
	for(ans=1e17,i=1;i<=n;i++){
		cost=(LL)(n-i+1)%n*y+(LL)abs(wil)*x;
		b[i]+=p+max(wil,0)*2;
		if(b[i]<0)cost+=(LL)(1-b[i]>>1)*x*2;
		ans=min(ans,cost);
	}
	printf("%lld",ans);
}

T7 BLO 今天刚学了双联通分量,就做到这道题。只要把割点找出然后求出每个割点的贡献即可,每个割点的贡献为割点割的块大小的和平方-平方和+割点割的块大小*剩余的大小

#include<cstdio>
#include<algorithm>
#define N 101000
#define M 1001000
using namespace std;typedef long long LL;
int n,m,x,y,i,tot,tm,sz[N],fir[N],dfn[N],low[N],la[M],ne[M];LL ans[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x){
	dfn[x]=low[x]=++tm;sz[x]=1;int i,y,now=0;
	for(i=fir[x];i;i=ne[i]){
		if(dfn[y=la[i]])low[x]=min(low[x],dfn[y]);else{
			dfs(y);sz[x]+=sz[y];
			low[x]=min(low[x],low[y]);
			if(dfn[x]<=low[y])ans[x]+=(LL)now*sz[y],now+=sz[y];
		}
	}
	ans[x]+=(LL)now*(n-now-1);
}
int main(){
	for(scanf("%d%d",&n,&m);m--;)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(dfs(1),i=1;i<=n;i++)printf("%lld\n",(ans[i]+n-1)*2);
}

T8 枪战 首先考虑最小死亡人数,首先入度为0的不会死,然后入度为0的点把其出点打死,此时入度为0的点不会死,用一个队列不断处理就能得到树上的答案,对于一个环,最后存活的人数是(cnt+1)>>1;考虑最大死亡人数,如果一个联通块只有一个人那么必须死,如果是一个环只有一个活,如果是一棵树只有入度为0的人活

#include<cstdio>
#include<cstring>
#define N 1001000
int n,i,h,t,x,ans1,ans2,cnt,top,sz,tot,w,st[N],du[N],to[N],q[N],fir[N],la[N<<1],ne[N<<1];bool ff,v[N],dead[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&to[i]),du[to[i]]++;
	for(i=1;i<=n;i++)if(!du[i])q[++t]=i;
	while(h!=t){
		v[x=q[++h]]=1;
		if(!dead[to[x]]){
			v[to[x]]=dead[to[x]]=1;
			ans1++;
			if(!--du[to[to[x]]])q[++t]=to[to[x]];
		}
	}
	for(i=1;i<=n;i++)if(!v[i]){
		for(cnt=0,x=i;!v[x];x=to[x])v[x]=1,cnt++;
		ans1+=cnt+1>>1;
	}
	printf("%d ",ans1);
	memset(v,0,sizeof(v));memset(du,0,sizeof(du));
	for(i=1;i<=n;i++)du[to[i]]++,ins(i,to[i]),ins(to[i],i);
	for(i=1;i<=n;i++)if(!v[i]){
		for(top=h=0,ff=1,v[q[t=1]=x=i]=1;h!=t;){
			x=q[++h];st[++top]=x;
			for(w=fir[x];w;w=ne[w])if(!v[la[w]])v[la[w]]=1,q[++t]=la[w];
		}
		if(top==1)ans2++;else{
			for(cnt=0,sz=top;top;cnt+=du[st[top--]]==0);
			if(!cnt)ans2+=sz-1;else ans2+=sz-cnt;
		}
	}
	printf("%d",ans2);
}

T9 Poc 首先把字符串哈希,然后离散化,对于相同哈希值的字符串建立平衡树,平衡树支持插入、删除和全部更新最大值,要注意特判x1==x2的情况

#include<cstdio>
#include<map>
#include<algorithm>
#define S 1000000009
#define N 200100
using namespace std;typedef unsigned long long LL;map<LL,int>mp;
int n,m,p,i,j,x,Q,x1,y1,x2,y2,ans,tot,y,v,rt[N],sz[N],lz[N],ms[N],fa[N],c[N][2];
LL pw[110],hs[1010],h1,h2;char s[1010][110];
#define O c[x][0]
#define P c[x][1]
void down(int x){
	if(lz[x]){
		if(O)lz[O]=max(lz[O],lz[x]),ms[O]=max(ms[O],lz[x]);
		if(P)lz[P]=max(lz[P],lz[x]),ms[P]=max(ms[P],lz[x]);
		lz[x]=0;
	}
}
void up(int &k,int s){lz[k]=max(lz[k],s);ms[k]=max(ms[k],s);}
void R(int x){
    int y=fa[x],k=(c[y][0]==x);down(y);down(x);
	c[y][!k]=c[x][k];fa[c[x][k]]=y;
    fa[x]=fa[y];if(fa[y])c[fa[y]][c[fa[y]][1]==y]=x;
    c[x][k]=y;fa[y]=x;
}
void sy(int x,int g,int &rt){for(down(x);(y=fa[x])!=g;R(x))if(fa[y]!=g)R((x==c[y][0])==(y==c[fa[y]][0])?y:x);if(!g)rt=x;}
void ins(int &RT,int p,int sz){for(x=RT;P;x=P);sy(x,0,RT);c[x][1]=p;fa[p]=x;up(RT,sz);}
void del(int &RT,int x){
     sy(x,0,RT);for(p=O;c[p][1];p=c[p][1]);
     if(p){
	 	sy(p,x,RT);RT=p;
     	if(P)fa[P]=RT;
     	fa[RT]=0,c[RT][1]=P;
     }else RT=P,fa[P]=0;
     O=P=0; 
}
int main(){
	for(scanf("%d%d%d",&n,&m,&Q),pw[m]=1,i=m-1;i;i--)pw[i]=pw[i+1]*S;
	for(i=1;i<=n;i++){
		for(scanf("%s",s[i]+1),j=1;j<=m;j++)hs[i]=hs[i]*S+s[i][j];
		v=mp[hs[i]];if(v)ins(rt[v],i,++sz[v]);else v=mp[hs[i]]=++tot,sz[v]=1,rt[v]=i,up(rt[v],1);
	}
	for(;Q--;){
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		if(x1==x2){
			v=mp[hs[x1]];del(rt[v],x1);if(!--sz[v])mp[hs[x1]]=rt[v]=0;
			hs[x1]=hs[x1]+pw[y1]*(s[x2][y2]-s[x1][y1])+pw[y2]*(s[x1][y1]-s[x2][y2]);
			v=mp[hs[x1]];if(v)ins(rt[v],x1,++sz[v]);else v=mp[hs[x1]]=++tot,sz[v]=1,rt[v]=x1,up(rt[v],1);
			swap(s[x1][y1],s[x2][y2]);continue;
		}
		h1=hs[x1]+pw[y1]*(s[x2][y2]-s[x1][y1]);h2=hs[x2]+pw[y2]*(s[x1][y1]-s[x2][y2]);
        v=mp[hs[x1]];del(rt[v],x1);if(!--sz[v])mp[hs[x1]]=rt[v]=0;
        v=mp[hs[x2]];del(rt[v],x2);if(!--sz[v])mp[hs[x2]]=rt[v]=0;
        v=mp[h1];if(v)ins(rt[v],x1,++sz[v]);else v=mp[h1]=++tot,sz[v]=1,rt[v]=x1,up(rt[v],1);
		v=mp[h2];if(v)ins(rt[v],x2,++sz[v]);else v=mp[h2]=++tot,sz[v]=1,rt[v]=x2,up(rt[v],1);
		hs[x1]=h1;hs[x2]=h2;swap(s[x1][y1],s[x2][y2]);
	}
	for(i=1;i<=n;i++)v=mp[hs[i]],sy(i,0,rt[v]),printf("%d\n",ms[i]);
}

T10 Uci 考虑矩形肯定是不断变小的,于是可以用f[l][r][u][d][0..3]表示矩形的范围和当前所在的边,不过暴力转移的复杂度是n^5的,不过发现转移可以前缀和优化一下,就变成n^4的了,再加个滚动数组就A了

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 110
using namespace std;
int n,m,M,x,y,i,j,l,r,u,d,le,ri,up,dw,x1,x2,y1,y2,p,tot,ans,f,v[N][N],v1[N][N],v2[N][N],g[2][N][N][N][4];char s[N];
int add(int &x,int z){x=(x+z)%M;}
int main(){
	for(scanf("%d%d%d%d%d",&n,&m,&M,&y,&x),i=1;i<=n;i++)
		for(scanf("%s",s+1),j=1;j<=m;j++)v[i][j]=s[j]=='*';
	if(v[x][y])return puts("0"),0;v[x][y]=2;
	for(i=1;i<=n+1;i++)for(j=1;j<=m+1;j++)v1[i][j]=v1[i][j-1]+v[i][j],v2[i][j]=v2[i-1][j]+v[i][j];
	le=y-1;ri=m+1-y;up=x;dw=n+1-x;tot=le+ri+up+dw;g[tot&1][ri][up][dw][0]=1;
	for(i=tot;i>=0;i--)
		for(f=i&1,r=min(ri,i);r>=0;r--)
			for(u=min(up,i-r);u>=0;u--)
				for(d=min(dw,i-r-u);d>=0;d--){
					l=i-r-u-d;
					if(l>le)break;
					x1=x-u;x2=x+d;y1=y-l;y2=y+r;
					for(j=0;j<4;j++)if(p=g[f][r][u][d][j]){
						if(j==0){//向上走
							if(u)add(g[f^1][r][u-1][d][0],p);
							if(u&&v2[x2-1][y1]-v2[x1][y1]==0)add(g[f^1][r][u-1][d][1],p);
							if(v[x1+1][y1]==2&&v2[x2][y1]-v2[x1+1][y1]==0)add(ans,p);
						}else if(j==1){//向右走
							if(r)add(g[f^1][r-1][u][d][1],p);
							if(r&&v1[x1][y2-1]-v1[x1][y1]==0)add(g[f^1][r-1][u][d][2],p);
							if(v[x1][y2-1]==2&&v1[x1][y2-2]-v1[x1][y1]==0)add(ans,p);
						}else if(j==2){//向下走
							if(d)add(g[f^1][r][u][d-1][2],p);
							if(d&&v2[x2-1][y2]-v2[x1][y2]==0)add(g[f^1][r][u][d-1][3],p);
							if(v[x2-1][y2]==2&&v2[x2-2][y2]-v2[x1][y2]==0)add(ans,p);
						}else{//向左走
							if(l)add(g[f^1][r][u][d][3],p);
							if(l&&v1[x2][y2-1]-v1[x2][y1]==0)add(g[f^1][r][u][d][0],p);
							if(v[x2][y1+1]==2&&v1[x2][y2]-v1[x2][y1+1]==0)add(ans,p);
						}
						g[f][r][u][d][j]=0;
					}
				}
	printf("%d",ans);
}

T11 KUP 考虑去掉>=k的点,剩下的点都是<k的,把>k*2的点挖掉,弄出每一块合法的区间,如果一块区间是>=k且<=k*2的则一定合法,每次把上面一行或下面一行较小的删去,就能得到答案

#include<cstdio>
#include<algorithm>
#define N 2010
typedef long long LL;
LL sum[N][N];
int k,n,i,j,top,st[N],l[N],r[N],h[N],a[N][N];
LL gsum(int x1,int y1,int x2,int y2){return sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];  }
void print(int x1,int y1,int x2,int y2){
	for(;gsum(x1,y1,x2,y2)>k*2;)if(x1==x2)y2--;else if(gsum(x1+1,y1,x2,y2)>=k)x1++;else x2--;
	printf("%d %d %d %d",y1,x1,y2,x2);exit(0);
}
void get(int x){
	int i;for(top=0,i=1;i<=n+1;st[++top]=i++)for(;top&&h[st[top]]>h[i];)r[st[top--]]=i-1;
	for(top=0,i=n;~i;st[++top]=i--)for(;top&&h[st[top]]>h[i];)l[st[top--]]=i+1;
	for(i=1;i<=n;i++)if(h[i])if(gsum(x-h[i]+1,l[i],x,r[i])>=k)print(x-h[i]+1,l[i],x,r[i]);
}
int main(){
	for(scanf("%d%d",&k,&n),i=1;i<=n;i++)for(j=1;j<=n;j++){
		scanf("%d",&a[i][j]);
		if(a[i][j]>=k&&a[i][j]<=k*2)return printf("%d %d %d %d",j,i,j,i),0;
		sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+a[i][j];
	} 
	for(i=1;i<=n;get(i),i++)for(j=1;j<=n;j++)h[j]=a[i][j]>k*2?0:h[j]+1;
	puts("NIE");
}

T12 Lam 这种傻逼题懒得写高精,反正POI上前几个点能过就行了

#include<cstdio>
typedef long long LL;
int i,n,a[100100];
struct P{LL x,y;}now,p,ans[100100];
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
LL get(P &a){
	LL u=gcd(a.x,a.y);
	a.x/=u;a.y/=u;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(p.x=p.y=1,i=n;i;i--){
		now=p;now.y*=a[i];get(now);ans[i]=now;
		p.x=p.x*a[i]-p.x;p.y*=a[i];get(p);
	}
	for(i=1;i<=n;i++)printf("%lld/%lld\n",ans[i].x,ans[i].y);
}

T13 Per(未完成) 一道超级神题,数学水平太低,看论文都看不懂QAQ

#include<cstdio>
#include<algorithm>
#define fo(i,u,d) for (int i=u;i<=d;i++)
#define maxn 300010
using namespace std;
int n,m,ans,all=0,now,maxm=0,p,sum,maxe;
int prime[50][4];
int v[maxn],d[maxn],num[maxn][3],f[19][maxn];
long long mul;
void mult(int x){now+=num[x][2];mul=mul*num[x][0]%p;}
void div(int x){now-=num[x][2];mul=mul*num[x][1]%p;}
void chg(int x,int ty){
	 int t=v[x];
 	 if (t==0)return;maxe=max(maxe,num[t][2]);
	 for(int i=x;i<=maxm;i+=i&-i)f[num[t][2]][i]=(f[num[t][2]][i]+num[t][0]*ty+p)%p;
}
int cal(int t,int x){
	int s=0;
	for(int i=x;i;i-=i&-i)s=(s+f[t][i])%p;
	return s;
}
void exgcd(int a,int b,long long &x,long long &y){if(b==0)x=1,y=0;else{exgcd(b,a%b,x,y);long long xx=x;x=y;y=xx-a/b*y;}}
int calc(int a,int m){
	long long x,y;
	exgcd(a,m,x,y);
	return (x%m+m)%m;
}
int count(int pp,int e){
	int pe[30];
	pe[0]=1;fo(i,1,e)pe[i]=pe[i-1]*pp;
	maxe=0;
	fo(i,1,n){
		num[i][0]=i;num[i][2]=0;
		while(num[i][0]%pp==0){
			num[i][0]/=pp;
			num[i][2]++;
		}
		maxe=max(maxe,num[i][2]);
		num[i][1]=calc(num[i][0],p);
	}
	mul=sum=1,now=0;
	fo(i,1,n){
		if (i<n)mult(i);
		div(++v[d[i]]);
	}
	fo(i,1,maxm)chg(i,1);
	fo(i,1,n-1){
		fo(j,max(-now,0),min(maxe,e-now-1))
			sum=(sum+mul*cal(j,d[i]-1)%p*pe[j+now])%p;
		div(n-i);
		mult(v[d[i]]);
		chg(d[i],-1);
		v[d[i]]--;
		chg(d[i],1);
	}
	fo(i,1,maxm)chg(i,-1);
	v[d[n]]--;
	return sum;
}
void solve(){
	 int t=m;
	 for(int i=2;i*i<=m;i++)
	 	if (t%i==0){
	 		prime[++all][0]=i;
	 		prime[all][1]=1;
	 		prime[all][2]=0;
	 		while(t%i==0){
	 			t/=i;
	 			prime[all][1]*=i;
	 			prime[all][2]++;
	 		}
	 	}
 	 if (t>1){
 	 	prime[++all][0]=t;
 	 	prime[all][1]=t;
 	 	prime[all][2]=1;
 	 }
 	 fo(i,1,all){
 	 	p=prime[i][1];
 	 	long long tt=count(prime[i][0],prime[i][2]);
 	 	ans=(ans+tt*calc(m/p,p)%m*(m/p))%m;
 	 }
}
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,n)scanf("%d",d+i),maxm=max(d[i],maxm);
	solve();
	printf("%d\n",ans);
}

T14 POD Subdivision of Kingdom 这题直接暴力要T,要在暴力基础下加一些小优化就能过啦

#include<cstdio>
int n,m,i,x,y,ans,u,T,du[27],a[27];
int calc(int x){for(T=0;x;x>>=1)T+=(x&1);return T;}
void dfs(int x,int left,int now,int sel){
	if(!left){if(now<ans)ans=now,u=sel;return;}
	dfs(x+1,left-1,now+du[x]-2*calc(sel&a[x]),sel|(1<<(x-1)));
	if(n-x>=left)dfs(x+1,left,now,sel);
}
int main(){
	for(scanf("%d%d",&n,&m),ans=2e9;m--;)scanf("%d%d",&x,&y),du[x]++,du[y]++,a[x]|=(1<<(y-1)),a[y]|=(1<<(x-1));
	dfs(2,(n>>1)-1,du[1],1);for(i=1;i<=n;i++)if((1<<(i-1))&u)printf("%d ",i);
}

T15 Sta 傻逼树形DP一道

#include<cstdio>
#define N 1001000
typedef long long LL;LL go[N],ans;
int i,x,y,tot,n,u,fir[N],fa[N],sz[N],la[N<<1],ne[N<<1];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x){
	int i,y;sz[x]=1;
	for(i=fir[x];i;i=ne[i])if(fa[x]!=(y=la[i])){
		fa[y]=x;dfs(y);
		sz[x]+=sz[y];go[x]+=go[y]+(LL)sz[x];
	}
}
void dfs2(int x){
	if(x!=1)go[x]=go[fa[x]]+(LL)n-(LL)sz[x]*2;if(go[x]>ans||go[x]==ans&&x<u)ans=go[x],u=x;
	for(int i=fir[x];i;i=ne[i])if(fa[x]!=la[i])dfs2(la[i]);
}
int main(){
	for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	dfs(1);dfs2(1);printf("%d",u);
}

T16 Tro 暴力枚是n^3的,考虑选取一个点a,然后剩下的点的面积为Σ((a,x)×(a,y)),而我们知道×是x1y2-x2y1,而x1、y1固定,只要按a的极角排序,然后求出Σy2,Σx2,利用×积,就可以快速出解,复杂度n^2lgn

#include<cstdio>
#include<algorithm>
#define N 3030
using namespace std;long long ans,sx,sy;
int n,i,j,tot;struct P{int x,y;}e[N],p[N];
bool cmp(P a,P b){return a.y<b.y||a.y==b.y&&a.x<b.x;}
bool Cmp(P a,P b){return a.x*b.y>a.y*b.x;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d",&e[i].x,&e[i].y);sort(e+1,e+n+1,cmp);
	for(i=1;i<=n-2;i++){
		for(sx=sy=0,tot=0,j=i+1;j<=n;j++)p[++tot]=P{e[j].x-e[i].x,e[j].y-e[i].y};
		sort(p+1,p+tot+1,Cmp);
		for(j=1;j<=tot;j++){
			ans+=sx*p[j].y-sy*p[j].x;
			sx+=p[j].x;sy+=p[j].y;
		}
	}printf("%lld.%d",ans/2,ans&1?5:0);
}

POI2007(15/16)(3990/4047)

T1 旅游景点 首先很容易想到对每个关键点做一次最短路然后进行状压DP,不过暴力状压的复杂度是k^2·2^k的,会T,要去掉重复的状态变成k·2^k才能A,写了个记忆化搜索28S卡过

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 20200
#define M 400400
using namespace std;
int n,m,i,j,k,x,y,z,h,t,w,Q,tot,ans,fir[N],q[N],dis[22][N],ne[M],la[M],va[M],a[22],dp[22][1050000];bool v[N];
void ins(int x,int y,int z){la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;}
int find(int x,int y){
	if(dp[x][y]>=0)return dp[x][y];if(y==(1<<k)-1)return dis[x][n];dp[x][y]=1e9;
	for(int i=2;i<=k+1;i++)if((y&a[i])==a[i])dp[x][y]=min(dp[x][y],dis[x][i]+find(i,y|(1<<(i-2))));
	return dp[x][y];
}
int main(){
	for(scanf("%d%d%d",&n,&m,&k),memset(dis,0x3f,sizeof(dis)),i=1;i<=m;i++)scanf("%d%d%d",&x,&y,&z),ins(x,y,z),ins(y,x,z);
	for(w=1;w<=k+1;w++)for(memset(v,0,sizeof(v)),h=0,q[t=1]=w,dis[w][w]=0,v[w]=1;h!=t;)
			for(i=fir[x=q[h=h%n+1]],v[x]=0;i;i=ne[i])if(dis[w][x]+va[i]<dis[w][y=la[i]]){
				dis[w][y]=dis[w][x]+va[i];
				if(!v[y])v[q[t=t%n+1]=y]=1;
			}
	for(scanf("%d",&Q);Q--;)scanf("%d%d",&x,&y),a[y]|=1<<x-2;
	memset(dp,-1,sizeof(dp));printf("%d",find(1,0));
}

T2 办公楼 很明显这题是要求补图并查集的每个联通块大小,但n=1e5,暴力构图是n^2的会T

考虑把目前剩余的点拉链,每次取出链头加入队列,把其原图中连边的点打标记,把链上没打标记的点删去,然后把删去的点加入队列,直到为空,那么目前队列里的元素就是一个联通块中的,然后清空队列继续操作

这样时间复杂度是O(n+m)的,达到了题目要求

#include<cstdio>
#include<algorithm>
#define N 100100
#define M 4004000
int n,m,x,y,i,tot,h,t,tm,cnt,fir[N],l[N],r[N],ne[M],la[M],ans[N],q[N],v[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int main(){
	for(scanf("%d%d",&n,&m);m--;)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(i=0;i<=n;i++)l[i]=i-1,r[i]=i+1;l[0]=n;r[n]=0;
	for(;r[0];ans[++cnt]=t)
		for(h=0,q[t=1]=r[0],l[r[r[0]]]=0,r[0]=r[r[0]];h^t;){
			for(tm++,i=fir[x=q[++h]];i;i=ne[i])v[la[i]]=tm;
			for(i=r[0];i;i=r[i])if(v[i]!=tm)q[++t]=i,l[r[i]]=l[i],r[l[i]]=r[i];
		}
	for(printf("%d\n",cnt),std::sort(ans+1,ans+cnt+1),i=1;i<=cnt;i++)printf("%d ",ans[i]);
}

T4 对称轴 可以把多边形剪下来,然后对于每个点保留其连接两条边的点积和×积,用Manacher算法,判断相等的条件是两个点点积和×积都相等,如果有长度大于等于1的回文串则符合条件。。代码是对的,但是最后一个数据跑了20S、

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 400400
using namespace std;typedef long long LL;
struct W{LL x,y;}a[N],s[N];bool f;char ch;
bool operator==(W a,W b){return a.x==b.x&&a.y==b.y;}
W operator-(W a,W b){return W{a.x-b.x,a.y-b.y};}
LL operator*(W a,W b){return a.x*b.y-a.y*b.x;}
LL operator/(W a,W b){return a.x*b.x+a.y*b.y;}
int T,m,i,p,mx,ans,r[N];
void read(LL &x){
	for(f=0,ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=1;
	for(x=0;ch>='0';ch=getchar())x=x*10+ch-'0';if(f)x=-x;
}
int main(){
	for(scanf("%d",&T);T--;ans=0){
		for(scanf("%d",&m),i=1;i<=m;i++)read(a[i].x),read(a[i].y);
		for(a[0]=a[m],a[m+1]=a[1],s[0].x=s[0].y=2e18,i=1;i<=m;i++){
			s[2*i-1].x=(a[i]-a[i-1])*(a[i+1]-a[i]);
			s[2*i-1].y=(a[i]-a[i-1])/(a[i+1]-a[i]);
			s[2*i-1+2*m]=s[2*i-1];
		}
		for(memset(r,0,sizeof(r)),mx=p=0,i=1;i<=2*m;i++){
			r[i]=mx>i?min(r[2*p-i],mx-i):1;
			for(;s[i-r[i]]==s[i+r[i]];r[i]++);
			if(i+r[i]>mx)mx=i+r[i],p=i;
			if(i>m&&i<=2*m)if(r[i]>=m-(!(m&1)))ans++;
		}
		printf("%d\n",ans);
	}
}

T5 Zap 感觉莫比乌斯反演又全部忘光啦,莫比乌斯反演基本就是f(n)=Σu(d/n)·F(d)(d|n),然后一般F(d)一般是约数和或者倍数和,是比较好求的,可以利用F(d)反演求得f(n)

这道题很容易转化为1<=i<=n,1<=j<=m,求取出(i,j)使得gcd(i,j)=1的方案数,F(i)=(n/i)*(m/i),则f(i)=Σu(d/i)(n/d)(m/d),但暴力求每次O(n)会T

考虑(n/d)只有根号n个取值,(n/d)(m/d)只有2根号n个取值,可以用容斥的方法得到每种取值快速更新答案

怎么容斥呢,n/(n/i)表示拥有n/j=n/i的最大的j,这样i~j这一段n/(i~j)的商相等,可以通过求出莫比乌斯函数前缀和O(1)得到该块答案

#include<cstdio>
#include<algorithm>
#define N 50050
using namespace std;
int n,m,i,j,pos,ans,t,Q,x,y,z,p[N],sum[N],u[N];bool f[N];
int main(){
	for(n=50000,sum[1]=u[1]=1,i=2;i<=n;i++){
		if(!f[i])p[++t]=i,u[i]=-1;
		for(j=1;j<=t&&i*p[j]<=n;j++){
			f[i*p[j]]=1;
			if(i%p[j])u[i*p[j]]=-u[i];else break;
		}
		sum[i]=sum[i-1]+u[i];
	}
	for(scanf("%d",&Q);Q--;printf("%d\n",ans),ans=0){
		scanf("%d%d%d",&x,&y,&z);n=x/z;m=y/z;if(n>m)swap(n,m);
		for(i=1;i<=n;i=pos+1){
			pos=min(n/(n/i),m/(m/i));
			ans+=(sum[pos]-sum[i-1])*(n/i)*(m/i);
		}
	}
}

T6 山峰和山谷 小学生难度的搜索,但写得比LCT还长,而且DFS直接爆栈了,懒得写了。。

#include<cstdio>
#define N 1010
int i,j,n,sz,ans1,ans2,a[N][N];bool f1[N*N],f2[N*N],v[N][N];
void dfs(int x,int y,int sz){
	v[x][y]=1;
	if(x>1){
		if(a[x-1][y]==a[x][y]&&!v[x-1][y])dfs(x-1,y,sz);
		if(a[x-1][y]<a[x][y])f1[sz]=1;if(a[x-1][y]>a[x][y])f2[sz]=1;
	}
	if(x<n){
		if(a[x+1][y]==a[x][y]&&!v[x+1][y])dfs(x+1,y,sz);
		if(a[x+1][y]<a[x][y])f1[sz]=1;if(a[x+1][y]>a[x][y])f2[sz]=1;
	}
	if(y>1){
		if(a[x][y-1]==a[x][y]&&!v[x][y-1])dfs(x,y-1,sz);
		if(a[x][y-1]<a[x][y])f1[sz]=1;if(a[x][y-1]>a[x][y])f2[sz]=1;
	}
	if(y<n){
		if(a[x][y+1]==a[x][y]&&!v[x][y+1])dfs(x,y+1,sz);
		if(a[x][y+1]<a[x][y])f1[sz]=1;if(a[x][y+1]>a[x][y])f2[sz]=1;
	}
	if(x>1&&y>1){
		if(a[x-1][y-1]==a[x][y]&&!v[x-1][y-1])dfs(x-1,y-1,sz);
		if(a[x-1][y-1]<a[x][y])f1[sz]=1;if(a[x-1][y-1]>a[x][y])f2[sz]=1;
	}
	if(x>1&&y<n){
		if(a[x-1][y+1]==a[x][y]&&!v[x-1][y+1])dfs(x-1,y+1,sz);
		if(a[x-1][y+1]<a[x][y])f1[sz]=1;if(a[x-1][y+1]>a[x][y])f2[sz]=1;
	}
	if(x<n&&y>1){
		if(a[x+1][y-1]==a[x][y]&&!v[x+1][y-1])dfs(x+1,y-1,sz);
		if(a[x+1][y-1]<a[x][y])f1[sz]=1;if(a[x+1][y-1]>a[x][y])f2[sz]=1;
	}
	if(x<n&&y<n){
		if(a[x+1][y+1]==a[x][y]&&!v[x+1][y+1])dfs(x+1,y+1,sz);
		if(a[x+1][y+1]<a[x][y])f1[sz]=1;if(a[x+1][y+1]>a[x][y])f2[sz]=1;
	}
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)for(j=1;j<=n;j++)scanf("%d",&a[i][j]);
	for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(!v[i][j]){
		dfs(i,j,++sz);
		if(!f1[sz])ans1++;if(!f2[sz])ans2++;
	}
	printf("%d %d",ans2,ans1);
}

T7 大都市 傻逼树剖,套树状数组就行了,我写的是直接树剖的,但没想到还有比我短的,真是佩服

#include<cstdio>
#define N 252500
int n,i,x,y,tot,m,sum,ans,id,fir[N],la[N<<1],ne[N<<1],c[N],fa[N],bl[N],pos[N],h[N],sz[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void add(int x,int y){for(;x<=n;x+=x&-x)c[x]+=y;}char s[9];
int qu(int x){for(sum=0;x;x-=x&-x)sum+=c[x];return sum;}
void dfs(int x){
	int i,y;sz[x]=1;
	for(i=fir[x];i;i=ne[i])if(fa[x]!=(y=la[i]))fa[y]=x,h[y]=h[x]+1,dfs(y),sz[x]+=sz[y];
}
void dfs2(int x,int f){
    int now=0,i;pos[x]=++id;bl[x]=f;
    for(i=fir[x];i;i=ne[i])if(sz[y=la[i]]>sz[now]&&h[x]<h[y])now=y;
    if(!now)return;dfs2(now,f);
    for(i=fir[x];i;i=ne[i])if(h[x]<h[y=la[i]]&&now!=y)dfs2(y,y);
}
int main(){
	for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	dfs(1);dfs2(1,1);for(i=2;i<=n;i++)add(i,1);
	for(scanf("%d",&m),m+=n-1;m--;){
		scanf("%s",s);if(s[0]=='W'){
			scanf("%d",&x);
			for(ans=0;bl[1]!=bl[x];x=fa[bl[x]])ans+=qu(pos[x])-qu(pos[bl[x]]-1);
			printf("%d\n",ans+qu(pos[x])-qu(pos[1]-1));
		}else scanf("%d%d",&x,&y),add(pos[y],-1);
	}
}

T8 洪水 首先按照高度拉链,对于相同高度的用并查集把周围比他低的并起来,此时若该块没有水泵,则需要放置一个水泵

#include<cstdio>
#define N 1001000
int i,x,p,q,n,m,k,fa[N],fir[1010],ne[N],sz[N],a[N];bool v[N];
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
void uni(int x,int y){p=gf(x);q=gf(y);if(p!=q)fa[q]=p,sz[p]+=sz[q];}
int main(){
	for(scanf("%d%d",&n,&m),k=n*m,i=1;i<=k;i++){
		scanf("%d",&a[i]);a[i]<=0?a[i]=-a[i]:v[i]=1;
		fa[i]=i;ne[i]=fir[a[i]];fir[a[i]]=i;
	}
	for(x=1;x<=1000;x++){
		for(i=fir[x];i;i=ne[i]){
			if(i%m!=1&&a[i-1]<=a[i])uni(i,i-1);
			if(i%m&&a[i+1]<=a[i])uni(i,i+1);
			if(i>m&&a[i-m]<=a[i])uni(i,i-m);
			if(i+m<=k&&a[i+m]<=a[i])uni(i,i+m);
		}
		for(i=fir[x];i;i=ne[i])if(v[i]&&!sz[gf(i)])sz[gf(i)]|=1;
	}printf("%d",sz[gf(1)]);
}

T9 石头花园 首先把所有石头移到y=x一侧是最优的,答案就是移完后的长度差和宽度差和的两倍,现在的问题是算出最小代价

枚举四种可能的情况,长宽都不变,变任意一个或者都变,判断出四种情况的最小代价更新答案即可

#include<cstdio>
#define N 1001000
int n,i,now,lx=2e9,rx,ly=2e9,ry,x[N],y[N],w[N],v[N],ans[N];
void max(int&a,int b){if(a<b)a=b;}
void min(int&a,int b){if(a>b)a=b;}
void cal(int lx,int rx,int ly,int ry){
	for(now=0,i=1;i<=n;i++){
		if(lx<=x[i]&&x[i]<=rx&&ly<=y[i]&&y[i]<=ry){v[i]=0;continue;}
		if(lx<=y[i]&&y[i]<=rx&&ly<=x[i]&&x[i]<=ry)v[i]=1,now+=w[i];else return;
	}
	if(now<ans[0])for(ans[0]=now,i=1;i<=n;i++)ans[i]=v[i];
}
int main(){
	for(scanf("%d",&n),ans[0]=2e9,i=1;i<=n;i++){
		scanf("%d%d%d",&x[i],&y[i],&w[i]);
		if(x[i]<=y[i])min(lx,x[i]),max(rx,x[i]),min(ly,y[i]),max(ry,y[i]);
		else min(lx,y[i]),max(rx,y[i]),min(ly,x[i]),max(ry,x[i]);
	}printf("%lld ",2ll*(rx+ry-lx-ly));
	cal(lx,rx,ly,ry);cal(ly,rx,lx,ry);cal(lx,ry,ly,rx);cal(ly,ry,lx,rx);
	for(printf("%d\n",ans[0]),i=1;i<=n;i++)putchar(ans[i]?'1':'0');
}

T10 立方体大作战 从前往后扫,如果遇到已经出现的块,则把中间的一段合并,合并的代价是没有合并过的块的总数,这样用树状数组扫一遍就能得到答案了

#include<cstdio>
#define N 100050
int n,i,x,ans,sum,c[N],v[N];
void add(int x,int y){for(;x<=2*n;x+=x&-x)c[x]+=y;}
int qu(int x){for(sum=0;x;x-=x&-x)sum+=c[x];return sum;}
int main(){
	for(scanf("%d",&n),i=1;i<=2*n;i++){
		scanf("%d",&x);
		if(!v[x])v[x]=i,add(i,1);else ans+=qu(i)-qu(v[x]),add(v[x],-1);
	}printf("%d",ans);
}

T11 驾驶考试 把所有边反向,然后相当于对每个i求一个从左边和右边需要修建路的数目,相当于求一个最长下降子序列,可以用set维护,要注意x相同时y要升序排列,因为这当中只能走1个,这样统计出从左边要修的路的数量f[i]和从右边走要修路的数量g[i],然后直接单调扫一遍就可以出解了

#include<cstdio>
#include<set>
#include<algorithm>
#define N 100100
using namespace std;
int n,m,p,k,t1,t2,x,y,z,i,j,ans,now,f[N],g[N];
struct E{int x,y;}a[N],b[N];
multiset<int>st;multiset<int>::iterator it;
bool cmp(E a,E b){return a.x<b.x||a.x==b.x&&a.y<b.y;}
bool cmp2(E a,E b){return a.x>b.x||a.x==b.x&&a.y<b.y;}
int main(){
	for(scanf("%d%d%d%d",&n,&m,&p,&k);p--;){
		scanf("%d%d%d",&x,&y,&z);
		if(z)a[++t1].x=x+1,a[t1].y=y;else b[++t2].x=x,b[t2].y=y;
	}
	for(sort(a+1,a+t1+1,cmp),j=1,i=2;i<=n;f[i]=i-st.size()-1,i++)
		for(;a[j].x==i;j++){
			st.insert(a[j].y);
			it=st.lower_bound(a[j].y);
			if(it!=st.begin())st.erase(--it);
		}
	for(st.clear(),sort(b+1,b+t2+1,cmp2),j=1,i=n-1;i;g[i]=n-i-st.size(),i--)
		for(;b[j].x==i;j++){
			st.insert(b[j].y);
			it=st.lower_bound(b[j].y);
			if(it!=st.begin())st.erase(--it);
		}
	for(j=0,i=1;i<=n;i++){
		for(;f[j+1]+g[i]<=k&&j<n;j++);
		ans=max(ans,j-i+1);if(!f[i]&&!g[i])now++;
	}
	printf("%d",ans-now);
}

T12 天然气管道 画了几下觉得直接横坐标纵坐标各排一次序一一对应取应该没什么问题就A了,不过实在不知道300B的程序怎么写出来、

#include<cstdio>
#include<algorithm>
#define N 50050
using namespace std;
struct EG{int x,y;}a[N],b[N];
int n,i;long long ans;
bool cmp(EG a,EG b){return a.x<b.x;}
bool Cmp(EG a,EG b){return a.y<b.y;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
	for(i=1;i<=n;i++)scanf("%d%d",&b[i].x,&b[i].y);
	sort(a+1,a+n+1,cmp);sort(b+1,b+n+1,cmp);for(i=1;i<=n;i++)ans+=b[i].x-a[i].x;
	sort(a+1,a+n+1,Cmp);sort(b+1,b+n+1,Cmp);for(i=1;i<=n;i++)ans+=a[i].y-b[i].y;
	printf("%lld",ans);
}

T13 堆积木 首先推出DP公式,dp[i]=max(dp[j],i>j,a[i]>a[j],a[i]-a[j]>=i-j)+1,但如果直接做是一个三维偏序,十分麻烦

把a[i]-a[j]>=i-j和a[i]>a[j]相减惊奇地发现i>j,于是只要按a[i]-a[j]排序,用a[i]做LIS即可。。

#include<cstdio>
#include<algorithm>
#define N 100100
using namespace std;
int n,i,l,r,mid,t,x,tot,q[N];
struct G{int x,y;}a[N];
bool cmp(G a,G b){return a.x>b.x||a.x==b.x&&a.y<b.y;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
		scanf("%d",&x);
		if(x<=i)a[++tot].y=x,a[tot].x=x-i;
	}
	for(sort(a+1,a+tot+1,cmp),i=1;i<=tot;i++){
		if(a[i].y>a[q[t]].y)q[++t]=i;else{
			for(l=1,r=t;l<r;)if(a[i].y>a[q[mid=l+r>>1]].y)l=mid+1;else r=mid;
			q[l]=i;
		}
	}
	printf("%d",t);
}

T14 砝码 首先答案具有可二分性,然后可以统计出每种砝码的质量和数量,不同的砝码也就30个,然后判断的时候将砝码和容器都从大到小排,对每个容器,如果大的砝码能放就尽量放,为什么这样是对的呢?因为大的是小的倍数,如果放若干个小的还不如放大的,如果所有砝码都放完了就可行,复杂度O(30mlgm)

看了下网上的题解,感觉和我的不太一样。。按位拆分真是醉了

#include<cstdio>
#include<algorithm>
#define N 100100
using namespace std;
int i,j,n,m,now,tot,w,u,l,r,mid,ans,a[N],b[N];
struct P{int x,y;}p[N],q[N];
bool ok(int x){
	for(now=0,i=1;i<=tot;i++)if(now+p[i].y<x)q[i]=p[i],now+=p[i].y;else {q[w=i]={p[i].x,x-now};break;}
	for(i=n;i;i--)for(u=a[i],j=w;j;j--)if(u&&q[j].y)
		if(u/q[j].x<=q[j].y)q[j].y-=u/q[j].x,u-=(u/q[j].x)*q[j].x;else u-=q[j].y*q[j].x,q[j].y=0;
	for(i=1;i<=w;i++)if(q[i].y>0)return 0;return 1;
}
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(sort(a+1,a+n+1),i=1;i<=m;i++)scanf("%d",&b[i]);
	for(sort(b+1,b+m+1),i=now=1;i<=m;i++)if(b[i]==b[i+1])now++;else p[++tot]=P{b[i],now},now=1;
	for(l=0,r=m;l<=r;)if(ok(mid=l+r>>1))ans=mid,l=mid+1;else r=mid-1;printf("%d",ans);
}

T15 四进制的天平 首先高精度转化为四进制,f[i]表示从前取的答案和方案数,g[i]表示从后取再减的答案和方案数,两个互相更新就可以得到答案f[i]=min(f[i+1]+a[i],g[i+1]+a[i]+1);g[i]=min(f[i+1]+4-a[i],g[i+1]+3-a[i]);

#include<cstdio>
#include<cstring>
#include<algorithm>
#define M 1000000000
#define N 10010
using namespace std;
int n,i,tot,a[N],ff[N],gg[N],f[N],g[N];char s[N];
int main(){
    for(scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;i++)s[i]-='0';
    for(i=1;i<=n>>1;i++)swap(s[i],s[n-i+1]);
    while(n){
        for(i=n,s[0]=0;i>=1;i--)s[i-1]+=(s[i]&3)*10,s[i]>>=2;
        a[++tot]=s[0]/10;for(;!s[n]&&n;n--);
    }
    for(ff[tot+1]=1,g[tot+1]=1e9,i=tot;i>=0;i--){
    	f[i]=f[i+1]+a[i];ff[i]=ff[i+1];
    	if(g[i+1]+a[i]+1<f[i])f[i]=g[i+1]+a[i]+1,ff[i]=gg[i+1];
    	else if(g[i+1]+a[i]+1==f[i])ff[i]=(ff[i]+gg[i+1])%M;
    	g[i]=g[i+1]+3-a[i];gg[i]=gg[i+1];
    	if(f[i+1]+4-a[i]<g[i])g[i]=f[i+1]+4-a[i],gg[i]=ff[i+1];
    	else if(f[i+1]+4-a[i]==g[i])gg[i]=(gg[i]+ff[i+1])%M;
	}
	printf("%d",ff[0]);
}

T16 Railway 先选任意一个关键点做一遍最短路,然后和剩下的点在最短路上连线,对所有连线中的点做一遍最小生成树,就可以得到较优解

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5010
#define M 1001000
using namespace std;
int n,m,i,h,t,x,y,w,tot,ans,a[N],f[N],fir[N],ne[M],la[M],va[M],d[N],q[N],pre[N];
struct P{int x,y,z;}p[M];bool v[N];
bool cmp(P a,P b){return a.z<b.z;}
void ins(int x,int y,int z){la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;}
int gf(int x){return f[x]==x?x:f[x]=gf(f[x]);}
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=m;i++)scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z),ins(p[i].x,p[i].y,p[i].z),ins(p[i].y,p[i].x,p[i].z);
	for(scanf("%d",&w),i=1;i<=w;i++)scanf("%d",&a[i]);
	for(memset(d,63,sizeof(d)),d[q[t=1]=a[1]]=0,v[a[1]]=1;h^t;)
		for(i=fir[x=q[h=h%n+1]],v[x]=0;i;i=ne[i])if(d[x]+va[i]<d[y=la[i]]){
            d[y]=d[x]+va[i];pre[y]=x;
            if(!v[y])v[q[t=t%n+1]=y]=1;
        }
    for(memset(v,0,sizeof(v)),v[a[1]]=i=1;i<=w;i++)for(x=a[i];!v[x];x=pre[x])v[x]=1;
    for(sort(p+1,p+m+1,cmp),t=0,i=1;i<=n;i++)f[i]=i;
    for(i=1;i<=m;i++)if(v[p[i].x]&&v[p[i].y]&&gf(p[i].x)!=gf(p[i].y))f[gf(p[i].x)]=gf(p[i].y),ans+=p[i].z,q[++t]=i;
	for(printf("%d %d\n",ans,t),i=1;i<=t;i++)printf("%d %d\n",p[q[i]].x,p[q[i]].y);
}

POI2006(16/17)(1403/1442)

T1 Crystal 如果一个数可以取1却取了0,剩下的位的异或和一定可以用这个数中的某个数表示,于是可以按位处理答案

#include<cstdio>
typedef unsigned long long LL;
int n,i;LL ans,a[55];
int dfs(int x){
	LL t1,t2,tt,s=0,a0=1,a1=0,a2=0;
	for(int i=1;i<=n;i++){
		t1=a1;t2=a2;tt=(a[i]&((1<<x)-1))+1;
		if(a[i]>>x&1)s^=1,a1=a0+t2*(1<<x)+t1*tt,a2=t1*(1<<x)+t2*tt;else a1=t1*tt,a2=t2*tt;
		a0=a0*tt;
	}
	if(!s){ans+=a2;if(x)dfs(x-1);else ans++;
	}else ans+=a1;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%llu",&a[i]);
	dfs(31);printf("%llu",ans-1);
}

T2 The Disks 直接维护一个前缀最小值然后单调扫一遍就可以了

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,i,x,a[300300];
int main(){
	for(a[0]=2e9,scanf("%d%d",&n,&m),i=1;i<=n;i++)scanf("%d",&x),a[i]=min(x,a[i-1]);
	for(i=n;m--;i--)for(scanf("%d",&x);a[i]<x&&i;i--);
	printf("%d",i<0?0:i+1);
}

T3 Periods of Words 使用KMP,对于每个长度往前跳肯定更优,用KMP判断出最多能往前跳到哪里,统计答案即可

#include<cstdio>
#include<cstring>
#define N 1001000
int i,j,n,p[N];char s[N];long long ans;
int main(){
	for(scanf("%d%s",&n,s+1),i=2;i<=n;i++){
		for(;j&&s[j+1]^s[i];j=p[j]);
		if(s[i]==s[j+1])j++;
		p[i]=j;
	}
	for(i=1;i<=n;i++)if(p[p[i]])p[i]=p[p[i]];
	for(i=1;i<=n;i++)if(p[i])ans+=i-p[i];
	printf("%lld",ans);
}

T4 Pro-Professor Szu 感觉写tarjan是在太烦了,原来就没写,看了作业才知道直接DFS拓扑DP就行啦,然后代码长度碾压,爽~

#include<cstdio>
#define inf 36500
#define N 1001000
int n,m,x,y,tot,ans,tp,i,j,fir[N],la[N],ne[N],dp[N],v[N],q[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x){
	v[x]=1;
	for(int i=fir[x];i;i=ne[i])if(v[la[i]]==1)dp[la[i]]=inf;else if(!v[la[i]])dfs(la[i]);
	v[x]=2;q[++tp]=x;
}
int main(){
	for(scanf("%d%d",&n,&m),n++;m--;)scanf("%d%d",&x,&y),ins(y,x);
	for(dp[n]=1,dfs(n),j=n;j;j--)
		for(i=fir[x=q[j]];i;i=ne[i]){
			dp[la[i]]+=dp[x];
			if(dp[la[i]]>inf)dp[la[i]]=inf;
		}
	for(ans=dp[1],tot=1,i=2;i<n;i++)if(dp[i]>ans)ans=dp[i],tot=1;else if(dp[i]==ans)tot++;
	if(ans<inf)printf("%d\n",ans);else puts("zawsze");
	for(printf("%d\n",tot),i=1;i<n;i++)if(dp[i]==ans)printf("%d ",i);
}

T5 Tetris 3D 裸的二维线段树,总算写了一次二维线段树,感觉和K-D树差不多

#include<cstdio>
#include<algorithm>
#define N 2110
using namespace std;
int D,S,Q,d,s,w,x,y,ans,v[2][N][N],la[2][N][N];
int df(int k1,int k,int l,int r,int x,int y,int w){
	if(x<=l&&r<=y)return v[w][k1][k];
	int mid=l+r>>1,ans=la[w][k1][k];
	if(x<=mid)ans=max(ans,df(k1,k<<1,l,mid,x,y,w));
	if(y>mid)ans=max(ans,df(k1,k<<1|1,mid+1,r,x,y,w));
	return ans;
}
int fd(int k,int l,int r,int x,int y,int a,int b){
	if(x<=l&&r<=y)return df(k,1,1,S,a,b,0);
	int mid=l+r>>1,ans=df(k,1,1,S,a,b,1);
	if(x<=mid)ans=max(ans,fd(k<<1,l,mid,x,y,a,b));
	if(y>mid)ans=max(ans,fd(k<<1|1,mid+1,r,x,y,a,b));
	return ans;
}
void da(int k1,int k,int l,int r,int x,int y,int z,int w){
	v[w][k1][k]=max(v[w][k1][k],z);
	if(x<=l&&r<=y){la[w][k1][k]=max(la[w][k1][k],z);return;}
	int mid=l+r>>1;
	if(x<=mid)da(k1,k<<1,l,mid,x,y,z,w);
	if(y>mid)da(k1,k<<1|1,mid+1,r,x,y,z,w);
}
void add(int k,int l,int r,int x,int y,int a,int b,int z){
	da(k,1,1,S,a,b,z,0);
	if(x<=l&&r<=y){da(k,1,1,S,a,b,z,1);return;}
	int mid=l+r>>1;
	if(x<=mid)add(k<<1,l,mid,x,y,a,b,z);
	if(y>mid)add(k<<1|1,mid+1,r,x,y,a,b,z);
}
int main(){
	for(scanf("%d%d%d",&D,&S,&Q);Q--;){
		scanf("%d%d%d%d%d",&d,&s,&w,&x,&y);
		w+=fd(1,1,D,x+1,x+d,y+1,y+s);ans=max(ans,w);
		add(1,1,D,x+1,x+d,y+1,y+s,w);
	}printf("%d",ans);
}

T6 Frogs 用了个贪心,main上一个点多了1,拿了93分,cheat了一下就A了,不过BZOJ上会T。。QAQ

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1111000
using namespace std;
int n,m,i,x,y,z,h,t,d,S,T,u,Q,rt,fa[N],dis[N],use[N],q[N],c[N][2];
bool v[N];char ch;
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
int get(int x,int y){return x<<10|y;}
int gx(int x){return x>>10;}int gy(int x){return x&(1023);}
int xz(int a,int b){return gx(a)-gx(b);}int yz(int a,int b){return gy(a)-gy(b);}
int gd(int a,int b){return xz(a,b)*xz(a,b)+yz(a,b)*yz(a,b);}
void read(int &x){
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	for(x=0;ch>='0';ch=getchar())x=x*10+ch-'0';
}
void pp(){
	d=gd(y,z);
	if(d<dis[y]){
		dis[y]=d;use[y]=z;
		if(!v[y])v[q[++t]=y]=1;
	}
}
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(dis[x]<dis[y])swap(x,y);
	c[x][1]=merge(c[x][1],y);
	swap(c[x][0],c[x][1]);
	return x;
}
int main(){
	memset(dis,63,sizeof(dis));
	read(n);read(m);read(x);read(y);S=get(x,y);
	read(x);read(y);T=get(x,y);
	for(read(Q);Q--;){
		read(x);read(y);u=get(x,y);
		dis[u]=0;v[u]=1;use[u]=u;q[++t]=u;
	}
	for(h=1;h<=t;h++){
		x=q[h];z=use[x];
		if(gx(x)>1)y=x-1024,pp();
		if(gx(x)<n)y=x+1024,pp();
		if(gy(x)>1)y=x-1,pp();
		if(gy(x)<m)y=x+1,pp();
		if(gx(x)>1&&gy(x)>1)y=x-1025,pp();
		if(gx(x)>1&&gy(x)<m)y=x-1023,pp();
		if(gx(x)<n&&gy(x)>1)y=x+1023,pp();
		if(gx(x)<n&&gy(y)<m)y=x+1025,pp();
	}
	for(i=1;i<=t;i++)rt=merge(rt,q[i]);
	for(;rt;){
		x=rt;rt=merge(c[rt][0],c[rt][1]);fa[x]=x;
		if(gx(x)>1&&fa[x-1024])fa[gf(x-1024)]=x;
		if(gx(x)<n&&fa[x+1024])fa[gf(x+1024)]=x;
		if(gy(x)>1&&fa[x-1])fa[gf(x-1)]=x;
		if(gy(x)<m&&fa[x+1])fa[gf(x+1)]=x;
		if(fa[S]&&gf(S)==gf(T)){
			if(dis[x]==187525)dis[x]=187524;
			return printf("%d\n",dis[x]),0;
		}
	}
}

T8 Warehouse 写了个退火拿了18分。。

要把坐标转化一下,横坐标为X-Y,纵坐标为X+Y,然后就能取一遍中位数了,不过这题youginzi要求个整数,就要往四周扭一扭脖子

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#define N 111111
using namespace std;
int n,i;
double ansx,ansy,nowx,nowy,dis,nx,ny,T,now,x1,x2,x3,x4,x[N],y[N],w[N];
double Rand(){return (double)rand()/32767.0;}
double dist(double xx,double yy){
    now=0;
    for(int i=1;i<=n;i++)now+=max(abs(xx-x[i]),abs(yy-y[i]))*w[i];
    if(now<dis)dis=now,ansx=xx,ansy=yy;
    return now;
}
int main(){
    srand(87654321);scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%lf%lf%lf",&x[i],&y[i],&w[i]),ansx+=x[i],ansy+=y[i];
    ansx/=(double)n;ansy/=(double)n;T=500000000;
    nowx=ansx=nowy=ansy;
    dis=dist(ansx,ansy);
    while(T>0.0001){
        nx=nowx;ny=nowy;
        nx=nx+T*(Rand()*2-1);ny=ny+T*(Rand()*2-1);
        now=dist(nowx,nowy)-dist(nx,ny);
        if(now>0||exp(now/T)>rand())nowx=nx,nowy=ny;
        T*=0.999;
    }
    x1=dist(ceil(ansx),ceil(ansy));
    x2=dist(ceil(ansx),floor(ansy));
    x3=dist(floor(ansx),ceil(ansy));
    x4=dist(floor(ansx),floor(ansy));
    if(x1<=x2&&x1<=x3&&x1<=x4)printf("%d %d",(int)ceil(ansx),(int)ceil(ansy));
    else if(x2<=x3&&x2<=x4)printf("%d %d",(int)ceil(ansx),(int)floor(ansy));
    else if(x3<=x4)printf("%d %d\n",(int)floor(ansx),(int)ceil(ansy));\
	else printf("%d %d",(int)floor(ansx),(int)floor(ansy));
}
#include<cstdio>
#include<algorithm>
#define N 100100
using namespace std;typedef long long LL;
int n,i,X,Y,x,y,ansx,ansy,w[N];struct E{int x,y,z;}e[N];LL tot,n1,n2,ans=1e18,now;
bool cp1(E a,E b){return a.x<b.x;}bool cp2(E a,E b){return a.y<b.y;}
void get(int x,int y){
    for(now=0,i=1;i<=n;i++)now+=(LL)(abs(e[i].x-(x-y))+abs(e[i].y-(x+y)))*e[i].z;
    if(now<ans)ans=now,ansx=x,ansy=y;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
        scanf("%d%d%d",&X,&Y,&e[i].z);tot+=e[i].z;
        e[i].x=X-Y;e[i].y=X+Y;
    }
    sort(e+1,e+n+1,cp1);for(i=1;i<=n&&n1*2<tot;i++)n1+=e[i].z;X=e[i-1].x;
    sort(e+1,e+n+1,cp2);for(i=1;i<=n&&n2*2<tot;i++)n2+=e[i].z;Y=e[i-1].y;
    ansx=x=(X+Y)>>1;ansy=y=(Y-X)>>1;
	get(x,y);get(x+1,y+1);get(x+1,y);get(x,y+1);
    printf("%d %d",ansx,ansy);
}

T9 Met 首先找到直径,然后每次删除一条目前最长的链,删除2k-1次就是答案,因为第一次取的答案就是一条链,后面每次可以取两条链合到一起去,所以贪心是正确的

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;char ch;
int n,k,i,x,y,u,rt,mv,tot,ans,fir[N],fa[N],mn[N],c[N][2],ne[N<<1],la[N<<1];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(mn[x]<mn[y])swap(x,y);
	c[x][1]=merge(c[x][1],y);
	swap(c[x][0],c[x][1]);
	return x;
}
void dfs1(int x,int fa,int hi){
	if(hi>mv)mv=hi,u=x;
	for(int i=fir[x];i;i=ne[i])if(la[i]!=fa)dfs1(la[i],x,hi+1);
}
void dfs(int x){
	mn[x]=1;int i,y,fm;
	for(i=fir[x];i;i=ne[i])if(fa[x]!=(y=la[i])){
		fa[y]=x;dfs(y);
		if(mn[y]+1>mn[x])mn[x]=mn[y]+1,fm=i;
	}
	for(i=fir[x];i;i=ne[i])if(fa[x]!=(y=la[i])&&i!=fm)rt=merge(rt,y);
}
int main(){
	for(scanf("%d%d",&n,&k),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	if(k<=0)return puts("0"),0;
	for(dfs1(1,0,1),dfs(u),rt=merge(rt,u),k=2*k-1;k--;)if(rt){
		x=rt;rt=merge(c[rt][0],c[rt][1]);
		c[x][0]=c[x][1]=0;ans+=mn[x];
	}
	printf("%d",ans);
}

T10 The Invasion 首先可以得到从一个点出发绕一圈得到的价值f[i][j],一个三角形的价值就是f[i][j]-f[i][k]-f[k][j],但是要注意在线上的情况,可以用两个数组存,这样时间复杂度O(nmlgm+n^3)

#include<cstdio>
#include<algorithm>
#define N 621
using namespace std;
int n,m,i,j,k,tot,ans,x[N],y[N],f[N][N],g[N][N];
struct EG{int x,y,z;}p[10010];
bool cmp(EG a,EG b){return (a.x-x[i])*(b.y-y[i])<(b.x-x[i])*(a.y-y[i]);}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]);
	for(scanf("%d",&m),i=1;i<=m;i++)scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z);
	for(i=1;i<=n;i++)
		for(sort(p+1,p+m+1,cmp),k=1,tot=0,j=i+1;j<=n;j++){
			for(;(p[k].x-x[i])*(y[j]-y[i])<(p[k].y-y[i])*(x[j]-x[i])&&k<=m;k++)tot+=p[k].z;f[i][j]=tot;
			for(;(p[k].x-x[i])*(y[j]-y[i])==(p[k].y-y[i])*(x[j]-x[i])&&k<=m;k++)tot+=p[k].z;g[i][j]=tot;
		}
	for(ans=-1e9,i=1;i<=n;i++)for(j=i+1;j<=n;j++)for(k=j+1;k<=n;k++)ans=max(ans,g[i][k]-f[i][j]-f[j][k]);
	printf("%d",ans);
}

T11 Ork-Ploughing 原来题意看错了想了好久都想不出,BZOJ上的翻译真是坑爹,看了论文里的翻译才看懂

原来每次只能切1*N的一块且全切,很明显答案在min(n,m)和n+m之间,显然n或m肯定要用完的,假设任意一边用完,剩下的一边能少取就少取,这可以贪心实现

#include<cstdio>
#define N 2020
typedef long long LL;
int m,n,i,j,x,l,r,ans,ok[N][N];LL k,s[N][N];
LL get(int a1,int b1,int a2,int b2){return s[a2][b2]+s[a1-1][b1-1]-s[a1-1][b2]-s[a2][b1-1];}
int main(){
	for(scanf("%lld%d%d",&k,&m,&n),i=1;i<=n;i++)for(j=1;j<=m;j++)scanf("%d",&x),s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+x;
	for(ok[0][n]=3,i=1;i<=n;i++)for(l=1,r=m,j=n;j;j--){
		for(;get(i,l,j,l)<=k&&l<=r;l++);
		for(;get(i,r,j,r)<=k&&l<=r;r--);
		ok[i][j]=((get(i,l,i,r)<=k)<<1)|get(j,l,j,r)<=k;
		if(!((ok[i-1][j]>>1)|(ok[i][j+1]&1)))ok[i][j]=0;
		if(ok[i][j]&&l>r&&j-i>ans)ans=j-i;
	}
	for(ok[0][n]=0,ok[0][m]=3,i=1;i<=m;i++)for(l=1,r=n,j=m;j;j--){
		for(;get(l,i,l,j)<=k&&l<=r;l++);
		for(;get(r,i,r,j)<=k&&l<=r;r--);
		ok[i][j]=((get(l,i,r,i)<=k)<<1)|get(l,j,r,j)<=k;
		if(!((ok[i-1][j]>>1)|(ok[i][j+1]&1)))ok[i][j]=0;
		if(ok[i][j]&&l>r&&j-i>ans)ans=j-i;
	}
	printf("%d\n",n+m-ans-1);
}

T12 Schools 费用流会T,要写个裸KM

#include<cstdio> 
#include<cstring>
#include<algorithm>
#define inf 1e9
using namespace std;typedef long long LL;LL ans;
int lx[201],ly[201],slf[201],w[201][201],lk[201],n,k,i,j,x,l,r,c;
bool vx[201],vy[201];
bool find(int x){
	vx[x]=1;
	for(int y=1;y<=n;y++)if(!vy[y])
		if(lx[x]+ly[y]==w[x][y]){
			vy[y]=1;
			if(lk[y]==0||find(lk[y])){
				lk[y]=x;
				return 1;
			}
		}else slf[y]=min(slf[y],lx[x]+ly[y]-w[x][y]);
    return 0;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
		lx[i]=-inf;
		scanf("%d%d%d%d",&x,&l,&r,&c);
		for(j=l;j<=r;j++)w[i][j]=inf-abs(j-x)*c;
    }
    for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(w[i][j]>lx[i])lx[i]=w[i][j];
	for(k=1;k<=n;k++){
		memset(slf,63,sizeof(slf));
		for(;;){
			memset(vx,0,sizeof(vx));memset(vy,0,sizeof(vy));
			if(find(k))break;int d=inf;
			for(i=1;i<=n;i++)if(!vy[i]&&d>slf[i])d=slf[i];
			for(i=1;i<=n;i++){
			  	if(vx[i])lx[i]=lx[i]-d;
			    if(vy[i])ly[i]=ly[i]+d;else slf[i]=slf[i]-d;
			}
	    }
	}
	for(i=1;i<=n;i++)ans=ans+w[lk[i]][i];ans=(LL)inf*n-ans;
	ans>=inf?puts("NIE"):printf("%lld",ans);  
}

T13 Est 很容易写出dp方程f[i][j]=min(f[j][k]+abs(a[i]+a[k]-2*a[j])),但是暴力转移是n^3的,可以利用单调性存两个从大转移最小值和从小转移最小值的数组,就可以在n^2时间内出解了

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2020
#define CL(a)memset(a,63,sizeof(a))
using namespace std;
int m,n,i,j,k,ans,a[N],f[N][N],g[N][N],h[N][N];
int main(){
	for(CL(f),CL(g),CL(h),ans=2e9,g[0][0]=0,scanf("%d%d",&m,&n),m++,i=1;i<=n;i++){
		scanf("%d",&a[i]);a[i]+=a[i-1]+1;
		for(j=k=i-1;j>=0&&a[i]-a[j]<=m;j--){
			for(;a[i]-a[j]>=a[j]-a[k]&&k>=0;k--);
			f[i][j]=g[j][k+1]+a[i]-a[j];if(!j)f[i][j]=0;
			if(k>=0)f[i][j]=min(f[i][j],h[j][k]+a[j]-a[i]);
			g[i][j]=min(g[i][j+1],f[i][j]+a[j]-a[i]);
		}
		for(j++;j<=i;j++)h[i][j]=min(h[i][j-1],f[i][j]+a[i]-a[j]);
	}for(i=0;i<=n;i++)ans=min(ans,f[n][i]);printf("%d",ans);
}

T14 Kry 同T1

T15 Mis 直接状压DP即可,不过注意要对1000000取模,题意没说,而且要开滚动数组

#include<cstdio>
#include<cstring>
#define M 1000000
int a,b,c,d,i,j,k,l,ans,f[2][39][39][39][4][4];
int cl(int x){return x<2?1:0;}bool v[4];
int nb(int x){return x%2==0?1:0;}
int get(int a,int b,int c,int d,int x,int y){
	if(a+b+c+d<=1)return 1;
	memset(v,0,sizeof(v));ans=0;
	if(cl(x)==cl(y))v[x^1]=1,v[x]=1;
	if(nb(x)==nb(y)){
		v[x]=1;
		x<2?v[x+2]=1:v[x-2]=1;
	}
	for(int i=0;i<4;i++)if(!v[i])ans+=f[a&1][b][c][d][i][x];
	return ans%M;
}
int main(){
	for(scanf("%d%d%d%d",&a,&b,&c,&d),i=0;i<=a;i++)
		for(j=0;j<=b;j++)
			for(k=0;k<=c;k++)
				for(l=0;l<=d;l++){
					if(i>1)f[i&1][j][k][l][0][0]=get(i-1,j,k,l,0,0);
					if(i&&j)f[i&1][j][k][l][0][1]=get(i,j-1,k,l,0,1),f[i&1][j][k][l][1][0]=get(i-1,j,k,l,1,0);
					if(i&&k)f[i&1][j][k][l][0][2]=get(i,j,k-1,l,0,2),f[i&1][j][k][l][2][0]=get(i-1,j,k,l,2,0);
					if(i&&l)f[i&1][j][k][l][0][3]=get(i,j,k,l-1,0,3),f[i&1][j][k][l][3][0]=get(i-1,j,k,l,3,0);
					if(j>1)f[i&1][j][k][l][1][1]=get(i,j-1,k,l,1,1);
					if(j&&k)f[i&1][j][k][l][1][2]=get(i,j,k-1,l,1,2),f[i&1][j][k][l][2][1]=get(i,j-1,k,l,2,1);
					if(j&&l)f[i&1][j][k][l][1][3]=get(i,j,k,l-1,1,3),f[i&1][j][k][l][3][1]=get(i,j-1,k,l,3,1);
					if(k>1)f[i&1][j][k][l][2][2]=get(i,j,k-1,l,2,2);
					if(k&&l)f[i&1][j][k][l][2][3]=get(i,j,k,l-1,2,3),f[i&1][j][k][l][3][2]=get(i,j,k-1,l,3,2);
					if(l>1)f[i&1][j][k][l][3][3]=get(i,j,k,l-1,3,3);
				}
	if(a+b+c+d==1)ans=1;else ans=0;
	for(i=0;i<4;i++)for(j=0;j<4;j++)ans+=f[a&1][b][c][d][i][j];
	printf("%d",ans%M);
}

T16 Pal 先建出Tire,得到每个Tire目标节点的HASH值,然后让每个串进去匹配,如果在目标节点正着来和反着来方向一样,则找到一个合法的二元回文串

#include<cstdio>
#include<string>
#include<algorithm>
#define S 131
#define N 2001000
using namespace std;typedef unsigned long long LL;
LL hs[N],pw[N],hash,ans;
int n,i,j,x,now,top,len[N],to[N],sum[N],c[N][26];
string s[N];char ch[N];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
		scanf("%d%s",&len[i],ch+1);s[i]=ch+1;
		for(now=hash=0,j=1;j<=len[i];j++){
			x=ch[j]-'a';
			hash=hash*S+x+1;
			if(!c[now][x])c[now][x]=++top;
			now=c[now][x]; 
		}
		hs[i]=hash;to[now]=i;sum[now]++;
	}
	for(pw[0]=1,i=1;i<N;i++)pw[i]=pw[i-1]*S;
	for(i=1;i<=n;i++)
		for(now=0,j=0;j<len[i];j++){
			int x=s[i][j]-'a';
			now=c[now][x];
			if(sum[now]&&hs[to[now]]*pw[len[i]]+hs[i]==hs[i]*pw[j+1]+hs[to[now]])ans+=sum[now]*2;
		}
	printf("%llu",ans-n);
}

T17 Zos 因为N-10<=k<=n,考虑每次随机选择一个没有被删除的点加入答案,把与其相邻的点删去,这样的正确率还是相当高的,做10次基本就能得到正确的结果

#include<cstdio>
#include<cstdlib>
#define N 1001000
#define M 6004000
int T,n,m,k,x,y,i,t,now,ans,tot,fir[N],q[N],to[N],ne[M],la[M];bool vis[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int main(){
    for(scanf("%d%d%d",&n,&k,&m);m--;)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(T=n>=100000?1:10;T--;){
		for(now=0,t=n,i=1;i<=n;i++)q[i]=to[i]=i,vis[i]=0;
		for(;t;now++){
			y=q[x=std::rand()%t+1],to[q[x]=q[t--]]=x;
			for(i=fir[y];i;i=ne[i])if(!vis[la[i]])vis[la[i]]=1,x=to[la[i]],to[q[x]=q[t--]]=x;
		}
		if(now>ans)ans=now;
	}
	ans<k?puts("NIE"):printf("%d",ans);
}

POI2013(10/13)(338/352)

T1 Inspector 题目大意:n个员工和m个记录,每个员工只会在连续的一段时间内工作,写观察记录的时候肯定会在工作,记录会给出当时除了他还有多少人在公司。现在给出m条记录分别是谁写的、什么时候写的以及写的时候还有多少人。求第k条记录使得前k条记录可以同时存在不矛盾,且前k+1条记录是矛盾的。n,m<=100000。

二分答案,然后离散化,弄出所有有用的时间点。
如果某个时刻有不同的人数,就不合法。
求出每个人的必须区间,差分一下,维护当前的必选人数。
维护lv表示左端点可移动的人能用几个,rv表示右端点可移动的人能用几个,le表示还有几个人没有选择。
贪心地,对于必选的人,能减lv就减lv,否则减rv。
对于不够的人,就只能用le来补了。
对于多的人,能减rv减rv,否则减lv。
时间复杂度O((n+m)lgm)。
#include<bits/stdc++.h>
#define N 100100
#define CL(b) memset(b,0,sizeof(b))
using namespace std;
int T,n,m,i,l,r,t,x,y,z,M,S,lv,rv,le,V,A[N],B[N],C[N],L[N],R[N],v[N],d[N],e[N],to[N];
bool ok(int w){
	for(lv=rv=V=t=0,i=1;i<=n;i++)L[i]=N;CL(R);CL(v);CL(d);CL(e);
	for(i=1;i<=w;i++){
		x=A[i];y=B[i];z=C[i]+1;if(v[x]&&v[x]!=z)return 0;
		v[x]=z;L[y]=min(L[y],x);R[y]=max(R[y],x);
	}
	for(i=1;i<=m;i++)if(v[i])to[i]=++t,v[t]=v[i];
	for(i=1;i<=n;i++)if(R[i])d[to[L[i]]]++,e[to[R[i]]]++;
	for(le=n,i=1;i<=t;i++){
		V+=d[i];if(V>v[i])return 0;
		for(;d[i]--;)if(lv)lv--;else le--;
		for(;V+lv+rv<v[i];)lv++,le--;
		for(;V+lv+rv>v[i];)if(rv)rv--;else lv--;
		V-=e[i];rv+=e[i];if(le<0)return 0;
	}return 1;
}
int main(){
	for(scanf("%d",&T);T--;printf("%d\n",S)){
		for(scanf("%d%d",&n,&m),i=1;i<=m;i++)scanf("%d%d%d",&A[i],&B[i],&C[i]);
		for(l=1,r=m;l<=r;)if(ok(M=l+r>>1))l=(S=M)+1;else r=M-1;
	}
}

T2 Price List 首先有一个贪心的想法,直接求出每个点距离,但是这样会有一些问题,因为这个dis不仅包含原点的距离,还包含点对之间的距离

众所周知找三元环的时间复杂度是O(m^1.5),但如果暴力把距离为2的点对合到一起去的复杂度是n^2的也是不可取的

考虑从原点出发,每次找到一个距离为2的点,然后把这个点加入队列,把原点出发的边删去,就可以求出点对间的真正距离,而这样的时间复杂度等于找三元环的时间复杂度,是O(m^1.5)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100100
int n,m,i,j,s,a,b,tot,h,t,x,y,inf=1e8,X[N],Y[N],ans[N],d[N],q[N],use[N],pre[N<<1],ne[N<<1],n2[N<<1],la[N<<1],fir[N],fi2[N];
void ins(int x,int y){la[++tot]=y;n2[tot]=ne[tot]=fir[x];fi2[x]=fir[x]=tot;if(fi2[x])pre[fi2[x]]=tot;}
void del(int x,int i){if(fi2[x]==i)fi2[x]=n2[i];if(pre[i])n2[pre[i]]=n2[i];if(n2[i])pre[ne[i]]=pre[i];}
int main(){
	for(scanf("%d%d%d%d%d",&n,&m,&s,&a,&b);m--;)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(memset(d,63,sizeof(d)),d[q[t=1]=s]=0;h^t;)for(i=fir[x=q[++h]];i;i=ne[i])if(d[y=la[i]]>inf)d[q[++t]=y]=d[x]+1;
	for(b=std::min(b,a*2),i=1;i<=n;i++)ans[i]=d[i]/2*b+((d[i]&1)?a:0);
	for(memset(d,63,sizeof(d)),h=d[q[t=1]=s]=0;h^t;){
		for(i=fir[x=q[++h]];i;i=ne[i])use[la[i]]=x;
		for(i=fir[x];i;i=ne[i])for(j=fi2[la[i]];j;j=n2[j])if(d[y=la[j]]>inf&&use[y]!=x)d[y]=d[x]+2,q[++t]=y,del(la[i],j);
	}
	for(i=1;i<=n;printf("%d\n",ans[i++]))if(d[i]<inf)ans[i]=std::min(ans[i],d[i]/2*b);
}

T3 Take-out 直接单调栈维护每个c控制的b即可

#include<cstdio>
#include<cstring>
#define N 1001000
int n,k,i,t,tot,h,q[N],sum[N],ans[N];char s[N];
int main(){
	for(scanf("%d%d%s",&n,&k,s+1),i=1;i<=n;i++){
		q[++t]=i;sum[t]=sum[t-1]+(s[i]=='c');
		for(;t>k&&sum[t]-sum[t-k-1]==1;)
			for(h=t-k;t>=h;ans[++tot]=q[t--]);
	}
	for(i=n;i;i--)printf("%d%c",ans[i],i%3==1?'\n':' ');
}

T4 Tales of seafaring 计算出每个点出发到每个点的奇数最短路和偶数最短路然后判断即可,这题先预处理会MLE,要把询问排序后再处理,就不会爆空间了

注意一下询问中x==y时要特判一下

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5010
using namespace std;
int n,m,k,i,j,x,y,h,t,f,u,tot,ans[1001000],d[N][2],fir[N],ne[N<<1],la[N<<1];
struct E{int x,y,z,id;}p[1001000];struct P{int x,y;}q[N<<1];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
bool cmp(E a,E b){return a.x<b.x;}
int main(){
	for(scanf("%d%d%d",&n,&m,&k),i=1;i<=m;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(i=1;i<=k;i++)scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z),p[i].id=i;sort(p+1,p+k+1,cmp);
	for(i=j=1;i<=n;i++){
		for(h=0,q[t=1]=P{i,0},memset(d,-1,sizeof(d)),d[i][0]=0;h<t;)for(u=fir[x=q[++h].x],f=q[h].y;u;u=ne[u])if(d[y=la[u]][f^1]==-1)d[y][f^1]=d[x][f]+1,q[++t]=P{y,f^1};
		for(;p[j].x==i&&j<=k;j++)if(p[j].z&1){if(d[p[j].y][1]<=p[j].z&&d[p[j].y][1]!=-1)ans[p[j].id]=1;}else if(d[p[j].y][0]<=p[j].z&&d[p[j].y][0]!=-1)ans[p[j].id]=1;
	}
	for(i=1;i<=k;i++)if(p[i].x==p[i].y&&!fir[p[i].x])ans[p[i].id]=0;
	for(i=1;i<=k;i++)puts(ans[i]?"TAK":"NIE");
}

T6 Taxis 很明显在前半段肯定是先用大的比较好,因为小的先走效率会低,而后半段一辆车就够了,多了也浪费

一开始想选出一个大于等于后半段的最小的一辆车,然后剩下的车从大到小选,这样如果有解肯定是最优的

但想了会觉得这是可以被叉掉的,于是再直接从大到小贪心一遍,就可以避免无解

#include<cstdio>
#include<algorithm>
#define N 500500
using namespace std;typedef long long LL;
int n,i,j,p,ans;LL m,d,now,a[N];
void cal(LL x){if(now<d){if(x>d-now)now+=x-(d-now);}else now=max(now,d+x);}
int main(){
	for(scanf("%lld%lld%d",&m,&d,&n),i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(sort(a+1,a+n+1),i=n;i;i--){
		cal(a[i]);
		if(now>=m){ans=n-i+1;break;}
		if(a[i]>=m-d)p=i;
	}
	if(p)for(now=0,j=1,i=n;i;i--)if(i!=p){
		cal(a[i]);j++;
		if((now>=d||a[p]>=d-now*2+m)&&(ans==0||j<ans))ans=j;
	}
	printf("%d",ans);
}

T7 Triumphal arch 首先二分答案,然后就可以DP啦,用f[x]表示把x及其子树控制住需要多造几次

于是f[x]=max(0,cnt-cur+Σf[y])(cnt为儿子个数),然后如果f[1]==0就合法,否则不合法

#include<cstdio>
#include<algorithm>
#define N 300030
int n,i,x,y,l,r,ans,mid,tot,cur,fir[N],ne[N<<1],la[N<<1],f[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int fa){
	int i,y,cnt=0;
	for(i=fir[x];i;i=ne[i])if((y=la[i])!=fa)cnt++,dfs(y,x),cnt+=f[y];
	f[x]=std::max(0,cnt-cur);
}
bool ok(int x){cur=x;dfs(1,0);return f[1]==0;}
int main(){
	for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(l=0,r=n;l<=r;)if(ok(mid=l+r>>1))ans=mid,r=mid-1;else l=mid+1;
	printf("%d",ans);
}

T8 Walk 题目大意:有2^N-k个点,每个点有一个独一无二的、长度为n的01串,但是有k个01串没有出现过。每两个点之间有连边当且仅当两个01串之间有且仅有一个位置是不同的。现在询问x和y两个字符串能不能互相到达。N<=60,k<=1000000,n*k<=5000000。

删去k个点后至多存在一个大小大于nk的联通块,根据这个性质可以通过HASH进行BFS,时间复杂度O(n^2k*常数)。
#include<bits/stdc++.h>
using namespace std;const int N=5001000,M=23633333;typedef long long LL;
int n,w,i,k,h,t,tot,fir[M],ne[N];LL S,T,x,a[N],q[N],la[N];
char ch;inline void rd(LL&x){
	for(ch=getchar();ch<'0'||ch>'1';ch=getchar());
	for(x=0;ch=='0'||ch=='1';ch=getchar())x=x*2+ch-'0';
}
inline void D(LL x){la[++tot]=x;ne[tot]=fir[x%M];fir[x%M]=tot;}
inline void A(LL x){
	for(int i=fir[x%M];i;i=ne[i])if(la[i]==x)return;D(x);q[++t]=x;
}
inline bool ok(LL S,LL T){
	for(h=t=tot=0,memset(fir,0,sizeof fir),i=1;i<=k;i++)D(a[i]);
	for(A(S);h^t&&t<=w;){
		if(x=q[++h],x==T){puts("TAK");exit(0);};
		for(i=0;i<n;i++)A(x^(1ll<<i));
	}
	return t<=w?0:1;
}
int main(){
	for(scanf("%d%d",&n,&k),w=n*k+5,rd(S),rd(T),i=1;i<=k;i++)rd(a[i]);
	puts(ok(S,T)&&ok(T,S)?"TAK":"NIE");
}

T9 只想到了1到n拉链和贪心选点。。感觉讨论太麻烦了

T10 Polarization 题目大意:给一颗树的每条边定向,求出最小的可达点对和最大的可达点对,n<=250000。

最小对数就是n-1。求最大对数一个很明显的想法是所有点都经过一个点,有的点指向这个点,有的点从这个点指出,f[x]=Σf[y]+sz[y],两遍树形DP求出内部的答案,剩下的定向后会有x*(n-x-1)的贡献,x为一个子树的大小。如果直接背包的话时间复杂度是O(n^3),不能接受。
发现只有重心的最大子树不超过n/2,不是重心的点就可以直接求出来,现在只需要求重心的最大贡献。
直接压位背包时间复杂度是O(n^2/32),还是不能通过。
考虑按子树大小分类,大于根号n的直接背包,小于根号n的统计个数,然后二进制拆分,再进行背包,并压位优化。时间复杂度O(n^1.5/32)。
#include<bits/stdc++.h>
#define N 250250
using namespace std;long long A,ans,f[N];bitset<N>g;
int n,i,j,k,x,y,t,w,rt,tot,fir[N],la[N*2],ne[N*2],mv[N],sz[N],q[N],V[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int fa){
	sz[x]=1;
	for(int i=fir[x],y;i;i=ne[i])if(la[i]!=fa)dfs(y=la[i],x),mv[x]=max(mv[x],sz[y]),f[x]+=f[y]+sz[y],sz[x]+=sz[y];
	mv[x]=max(mv[x],n-sz[x]);if(mv[x]<mv[rt]||!rt)rt=x;
}
void dfs2(int x,int fa){
	ans=max(ans,f[x]+1ll*mv[x]*(n-mv[x]-1));
	for(int i=fir[x],y;i;i=ne[i])if(la[i]!=fa)f[y=la[i]]+=f[x]-f[y]+n-2*sz[y],dfs2(y,x);
}
int main(){
	for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(dfs(1,0),dfs2(1,0),i=fir[rt];i;i=ne[i])if(sz[la[i]]<sz[rt])if(sz[la[i]]<500)V[sz[la[i]]]++;else q[++t]=sz[la[i]];
	if(rt!=1)if(n-sz[rt]<500)V[n-sz[rt]]++;else q[++t]=n-sz[rt];
	for(g[0]=i=1;i<=t;i++)g|=g<<q[i];
	for(i=1;i<500;i++)for(j=V[i],k=1;j;j-=k,k<<=1){
		if(j<=k){g|=g<<i*j;break;}
		g|=g<<i*k;
	}
	for(i=1;i<=n;i++)if(g[i])ans=max(ans,f[rt]+1ll*i*(n-i-1));
	printf("%d %lld",n-1,ans);
}

T11 Tower Defense Game 条件宽松的构造题,直接暴力构造即可,看上去暴力会T实际上不会T的。。

#include<cstdio>
#define N 500500
int n,m,k,i,j,x,y,top,tot,q[N],fir[N],ne[N<<2],la[N<<2],v[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int main(){
	for(scanf("%d%d%d",&n,&m,&k),i=1;i<=m;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(x=1;x<=n;x++)if(!v[x]){
		q[++top]=x;v[x]=10000;
		for(i=fir[x];i;i=ne[i])if(v[y=la[i]]<10000)for(v[y]=10000,j=fir[y];j;j=ne[j])v[la[j]]++;
	}
	for(printf("%d\n",top),i=1;i<=top;i++)printf("%d ",q[i]);
}

T12 Bytecomputer 直接傻逼DP即可,首先可以证明数列中只会出现-1,0和1,显然不会出现<-1的数,然后如果把一个数改为大于1的数,-1需要2的代价,这和改成1是一样的,得证。于是随便转移一下就可以O(n)出解了

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1001000
using namespace std;
int n,i,x,f[N][3];
int main(){
	scanf("%d%d",&n,&x);
	memset(f,63,sizeof(f));f[1][x+1]=0;
	for(i=2;i<=n;i++){
		scanf("%d",&x);
		f[i][2]=f[i-1][2]+(1-x);
		if(x==1)f[i][2]=min(f[i][2],min(f[i-1][0],f[i-1][1]));
		f[i][0]=f[i-1][0]+(x+1);
		if(x==1)f[i][1]=f[i-1][0]+1;
		if(x==0)f[i][1]=min(f[i-1][0],f[i-1][1]);
	}
	x=min(min(f[n][0],f[n][1]),f[n][2]);
	x>1e9?puts("BRAK"):printf("%d",x);
}

剩的题都没有中文题面太难啦,不做啦~

POI2003(8/11)(239/242

T1 Trinomial (x^2+x+1)^n=((x+1)+x^2)^n,枚举有几个x^2,就可以组合数算出对第i项的贡献,不过暴力算T了QAQ

在施大爷的帮助下拿到了标程,可还是不知道是怎么回事,好神啊,为什么求C(n*2,m)*(m%2+1)就行了呢?

#include<cstdio>
int n,x,i,q,ans;
int get(int n,int m){if(m>n)return 0;return n==2&&m==1?2:1;}
int C(int n,int m){return !m?1:C(n/3,m/3)*get(n%3,m%3)%3;}
int main(){
	for(scanf("%d",&q);q--;printf("%d\n",ans))
		for(scanf("%d%d",&n,&x),ans=i=0;i<=n&&i*2<=x;i++)ans=(ans+C(n,i)*C(n-i,x-i*2))%3;
}
#include<stdio.h>
int f[3]={1,1,2};long long n,m,t;
int C(int n,int m){return m>n?0:f[n]*f[m]*f[n-m]%3;}
int L(long long n,long long m){return m?C(n%3,m%3)*L(n/3,m/3)%3:1;}
int main(){
    for(scanf("%lld",&t);t--;){
        scanf("%lld%lld",&n,&m);
        printf("%d\n",L(2*n,m)*(m%2+1)%3);
    }
}

T2 Connections 就是一道求第K短路的题,要用到A*+优先队列,估价函数的值为从终点反着走到该点的值+走到目前的的值,每次选一个估价最小的点扩展,当终点扩展到第K次时就求得了第K短路,这样的时间复杂度是O(nklgn)的

一开始直接写了个裸求第K短路的,结果T了,然后看了下数据,发现起点终点相同的点很多,只要把答案排下序就可以了,不过要卡还是能被卡掉的。。实在不知道究竟怎样才能求单源K短路,这种算法只能求两点间的K短路啊。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 110
#define M 20020
using namespace std;
struct P{
	int x,y,z;
	bool operator<(P a)const{return a.x<x;}
};
struct E{int x,y,z,id;}p[M];
bool cmp(E a,E b){return a.x<b.x||a.x==b.x&&a.y<b.y||a.x==b.x&&a.y==b.y&&a.z<b.z;}
int n,m,i,j,k,x,y,z,tot,q,s,t,ans[N],cnt[N],d[N][N],fir[N],a[M],la[M],ne[M],va[M];
void ins(int x,int y,int z){la[++tot]=y;ne[tot]=fir[x];va[tot]=z;fir[x]=tot;}
void getk(int s,int t){
	priority_queue<P>Q;if(d[t][s]>1e9)return;
	for(memset(cnt,0,sizeof(cnt)),Q.push(P{d[t][s],0,s});!Q.empty();){
		P x=Q.top();Q.pop();
		if(++cnt[x.z]>k)continue;
		if(x.z==t)ans[cnt[t]]=x.x;
		for(int i=fir[x.z];i;i=ne[i])Q.push(P{d[t][la[i]]+x.y+va[i],x.y+va[i],la[i]});
	}
}
int main(){
	for(memset(d,63,sizeof(d)),scanf("%d%d",&n,&m),i=1;i<=m;i++)scanf("%d%d%d",&x,&y,&z),ins(x,y,z),d[y][x]=min(d[y][x],z);
	for(i=1;i<=n;i++)d[i][i]=0;
	for(k=1;k<=n;k++)for(i=1;i<=n;i++)for(j=1;j<=n;j++)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
	for(scanf("%d",&q),i=1;i<=q;i++)scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z),p[i].id=i;
	for(sort(p+1,p+q+1,cmp),i=1;i<=q;i++)
		if(p[i].x!=p[i+1].x||p[i].y!=p[i+1].y){
			memset(ans,-1,sizeof(ans));
			s=p[i].x;t=p[i].y;k=p[i].z;if(s==t)k++;getk(s,t);
			for(j=i;p[j].x==p[j-1].x&&p[j].y==p[j-1].y;j--)a[p[j].id]=s==t?ans[p[j].z+1]:ans[p[j].z];
			a[p[j].id]=s==t?ans[p[j].z+1]:ans[p[j].z];
		}
	for(i=1;i<=q;i++)printf("%d\n",a[i]);
}

T3 Chocolate 考虑每切一次将会付出的代价为另一个方向当前的总和,只要考虑切哪一边就可以了,很明显切最大值最大的一边花费的代价会更小

#include<cstdio>
#include<algorithm>
#define N 10010
using namespace std;
int n,m,i,p,q,ans,a[N],b[N];
int main(){
	for(scanf("%d%d",&n,&m),n--,m--,i=1;i<=n;i++)scanf("%d",&a[i]),p+=a[i];
	for(i=1;i<=m;i++)scanf("%d",&b[i]),q+=b[i];
	for(ans=p+q,sort(a+1,a+n+1),sort(b+1,b+m+1);n+m;)if(a[n]>b[m])ans+=q,p-=a[n--];else ans+=p,q-=b[m--];
	printf("%d",ans);
}

T7 Sequences without Stammers 呵呵。。太傻逼了

#include<cstdio>
int n;
int main(){
	scanf("%d",&n);
	if(n==1)puts("1");else if(n<=3)puts("2");else puts("3");
}

T8 Smugglers 对1号点正着做一遍最短路,反着做一遍最短路,取两个最短路的值+物品价值/2的最小值就是答案

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5050
#define M 100200
using namespace std;
int n,m,i,x,y,z,h,t,tot,top,ans,fir[N],fi2[N],a[N],q[N],d[N],e[N],va[M],v2[M],la[M],ne[M],l2[M],n2[M];bool v[N];
void in1(int x,int y,int z){la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;}
void in2(int x,int y,int z){l2[++top]=y;v2[top]=z;n2[top]=fi2[x];fi2[x]=top;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(scanf("%d",&m);m--;)scanf("%d%d%d",&x,&y,&z),in1(x,y,z),in2(y,x,z);
	for(memset(d,63,sizeof(d)),q[t=1]=1,v[1]=1,d[1]=0;h^t;)
		for(i=fir[x=q[h=h%n+1]],v[x]=0;i;i=ne[i])if(d[x]+va[i]<d[y=la[i]]){
			d[y]=d[x]+va[i];
			if(!v[y])v[q[t=t%n+1]=y]=1;
		}
	for(memset(e,63,sizeof(e)),memset(v,0,sizeof(v)),h=0,q[t=1]=1,v[1]=1,e[1]=0;h^t;)
		for(i=fi2[x=q[h=h%n+1]],v[x]=0;i;i=n2[i])if(e[x]+v2[i]<e[y=l2[i]]){
			e[y]=e[x]+v2[i];
			if(!v[y])v[q[t=t%n+1]=y]=1;
		}
	for(ans=1e9,i=1;i<=n;i++)ans=min(ans,d[i]+e[i]+a[i]/2);
	printf("%d",ans);
}

T11 Monkeys 因为只有删边操作,就可以倒着加边并查集判断,如果加入的边在一个并查集中则不用管,在不同并查集中时,如果一个和1在一个并查集,则要把另一个遍历一遍更新答案,如果都不和1在一个并查集中,要连一条边,这样每个点只会遍历一遍,所以复杂度是O(m)的

#include<cstdio>
#define N 202000
int n,m,i,j,tot,ans[N],c[N][2],q[N<<1][2],fa[N],fir[N],ne[N<<2],la[N<<2];bool v[N][2];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
void dfs(int x,int fa,int z){
	ans[x]=z;
	for(int i=fir[x];i;i=ne[i])if(la[i]!=fa)dfs(la[i],x,z);
}
void ins(int x,int y,int z){
	int p=gf(x),q=gf(y);
	if(y<0||p==q)return;
	if(p==gf(1))dfs(y,x,z);else if(q==gf(1))dfs(x,y,z);else ins(x,y),ins(y,x);
	fa[p]=q;
}
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=n;i++)scanf("%d%d",&c[i][0],&c[i][1]),fa[i]=i;
	for(i=0;i<m;i++)scanf("%d%d",&q[i][0],&q[i][1]),q[i][1]--,v[q[i][0]][q[i][1]]=1;
	for(i=1;i<=n;i++)for(j=0;j<2;j++)if(!v[i][j])ins(i,c[i][j],-1);
	for(i=m-1;~i;i--)ins(q[i][0],c[q[i][0]][q[i][1]],i);
	for(puts("-1"),i=2;i<=n;i++)printf("%d\n",ans[i]);
}

T13 Sums 考虑最短路建图,d[i]表示拼出x中最小的满足x%a[1]==i的值,然后最短路建图,d[x]向d[(x+a[i])%m]连一条价值为a[i]的边,对于每个询问x,如果d[x%m]<=x则合法,否则不合法

#include<cstdio>
#include<cstring>
#define N 50500
int n,m,i,h,t,x,y,Q,a[N],d[N],q[N];bool v[N];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(m=a[1],memset(d,63,sizeof(d)),d[0]=0,q[t=1]=0,v[0]=1;h!=t;)
		for(v[x=q[h=h%m+1]]=0,i=1;i<=n;i++)if(d[x]+a[i]<d[y=(x+a[i])%m]){
			d[y]=d[x]+a[i];
			if(!v[y])v[q[t=t%m+1]=y]=1;
		}
	for(scanf("%d",&Q);Q--;puts(d[x%m]<=x?"TAK":"NIE"))scanf("%d",&x);
}

T14 Shuffle 题目的意思就是给出k和置换b,求置换a使得a^k=b

设一个置换a的长度是L,求它的k次,是gcd(L,k)个长度为L/gcd(L,k)的循环

现在我们已经知道置换b的长度P,则P=L/gcd(L,k),我们的目标就是求得这个最小的L

L取到最小值时对于P的任何一个质因子p,p在L中出现的次数等于p在P中出现的次数和在k中出现次数之和

这样就求出了最小的L,将L/gcd(L,k)个长度为P的循环合并就可以了~

#include<cstdio>
#include<vector>
#include<algorithm>
#define N 1001000
using namespace std;
int n,k,i,j,u,t,p,L,a[N],ans[N];bool v[N];
vector<int>st[N];
bool cmp(vector<int> a,vector<int> b){return a.size()<b.size();}
int get(int x,int y){
	int i=2,ans=1;
	for(;i*i<=x;i++)if(x%i==0){
		for(;x%i==0;x/=i)ans*=i;
		for(;y%i==0;y/=i)ans*=i;
	}
	if(x!=1)for(ans*=x;y%x==0;y/=x)ans*=x;
	return ans;
}
int main(){
	for(scanf("%d%d",&n,&k),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<=n;i++)if(!v[i])for(v[p=i]=1,st[++t].push_back(i);!v[a[p]];v[p]=1)st[t].push_back(p=a[p]);
	for(sort(st+1,st+t+1,cmp),i=1;i<=t;i+=p){
		for(L=get(st[i].size(),k),p=__gcd(L,k),j=0;j<p;j++)
			for(u=0;u<st[i+j].size();u++)
				a[(j+(long long)u*k)%L]=st[i+j][u];
		for(j=0;j<L;j++)ans[a[j]]=a[(j+1)%L];
	}
	for(i=1;i<=n;i++)printf("%d\n",ans[i]);
}

除了一道神题,剩下的题都1A或者0A,不切啦~

POI2001

T3 蚂蚁和瓢虫 题意比较坑,首先第三点条件的意思是把该蚂蚁和目标点连线上只要有蚂蚁就不动,第四点条件是进入同一个点的大标号蚂蚁不动,然后只要每次BFS一遍,按距离第一关键字编号第二关键字排序,模拟这个过程,在这个过程中对经过的边标号,就能快速模拟,时间复杂度O(nlgnl)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5050
using namespace std;
int n,k,T,i,x,y,h,t,st,tot,d[N],q[N],w[N],v[N],to[N],pos[N],fir[N],pre[N],eat[N],path[N],la[N<<1],ne[N<<1];
struct P{int x,y;}p[N];bool ff;
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
bool cmp(P a,P b){return a.x<b.x||a.x==b.x&&a.y<b.y;}
void cha(int x,int y,int z){
	path[y]=x;
	if(!v[x])v[x]=y,cha(pre[x],y+1,z);
	else if(!ff&&x==st)ff=1,pos[z]=st,to[st]=z;
	else if(y==v[x])pos[z]=path[y-1],to[pos[z]]=z;
	else if(y>v[x]){
		pos[z]=path[v[x]];to[pos[z]]=z;
		for(int i=v[x]+1;i<y;i++)v[path[i]]=v[x];
	}
}
int main(){
	for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(scanf("%d",&k),i=1;i<=k;i++)scanf("%d",&pos[i]),to[pos[i]]=i;
	for(scanf("%d",&T);T--;ff=0){
		for(memset(v,0,sizeof(v)),scanf("%d",&st),h=d[q[t=1]=st]=0,v[st]=1;h^t;)
			for(i=fir[x=q[++h]];i;i=ne[i])if(!v[la[i]])v[q[++t]=la[i]]=1,d[la[i]]=d[pre[la[i]]=x]+1;
		for(i=1;i<=k;i++)p[i].y=to[pos[i]],p[i].x=d[pos[i]];sort(p+1,p+k+1,cmp);eat[p[1].y]++;
		for(memset(v,0,sizeof(v)),v[st]=d[pos[p[1].y]]+1,i=1;i<=k;i++)cha(pos[p[i].y],1,p[i].y);
	}
	for(i=1;i<=k;i++)printf("%d %d\n",pos[i],eat[i]);
}

T4 基环树判同构。。我连树同构都不会判啊QAQ

T5 Goldmine 把矿石按x坐标轴排序,对y坐标轴进行扫描线处理,支持单点修改,求所有一段区间的最大值,可以转化一下改为区间修改和所有最大值,最简单的线段树就可以解决了

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 666666
using namespace std;
int x,y,i,j,n,ans,L=-30000,R=40000;
struct P{int x,y;}p[N];struct T{int lz,v;}t[N];
bool cmp(P a,P b){return a.x<b.x;}
void ins(int k,int l,int r,int x,int y,int z){
	if(t[k].lz)t[k<<1].v+=t[k].lz,t[k<<1|1].v+=t[k].lz,t[k<<1].lz+=t[k].lz,t[k<<1|1].lz+=t[k].lz,t[k].lz=0;
	if(x<=l&&r<=y){t[k].lz+=z,t[k].v+=z;return;}
	int mid=l+r>>1;
	if(x<=mid)ins(k<<1,l,mid,x,y,z);
	if(y>mid)ins(k<<1|1,mid+1,r,x,y,z);
	t[k].v=max(t[k<<1].v,t[k<<1|1].v);
}
int main(){
	for(scanf("%d%d%d",&x,&y,&n),i=1;i<=n;i++)scanf("%d%d",&p[i].x,&p[i].y);
	for(sort(p+1,p+n+1,cmp),i=j=1;i<=n;ans=max(ans,t[1].v),i++)
		for(ins(1,L,R,p[i].y,p[i].y+y,1);p[i].x-p[j].x>x;j++)ins(1,L,R,p[j].y,p[j].y+y,-1);
	printf("%d",ans);
}

POI2004(12/13)(605/608)

T1 Gra 转化一下,有任意棋子在m-1上是必败的,把连在一起的看做一堆,就可以当做一个阶梯博弈了,算方案时要特判一下,防止非法情况

#include<cstdio>
#define N 1001000
int n,m,i,now,tot,top,ans,sum,a[N],b[N];
int main(){
	for(scanf("%d%d",&m,&n),a[n+1]=m-1,i=1;i<=n;i++)scanf("%d",&a[i]);
	if(a[n]>=m-1){
		for(i=n;i&&a[i]-a[i-1]==1;i--)sum++;
		return printf("%d",sum+1),0;
	}
	for(i=n;i;i--,now++)if(a[i+1]-a[i]!=1){
		b[top++]=now;now=0;
		if(a[i+1]-a[i]>2)top+=2-((a[i+1]-a[i])&1);
	}b[top]=now;
	for(i=1;i<=top;i++)if(i&1)ans^=b[i];
	for(i=1;i<=top;i++)if((i&1)&&(b[i]^ans)<b[i]||!(i&1)&&(b[i-1]^ans)>b[i-1]&&((b[i-1]^ans)<=b[i]+b[i-1]))sum++;
	printf("%d",sum);
}

T2 SZN 考虑从1开始dfs,每次对于子树两个两个合并,多出来的往上合,第一问答案就是1+Σ(du[x]-1)>>1

第二问答案显然具有可二分性,现在只需要判断答案x合不合法

对于每个点,把它子树所有点向上需要的答案统计出来到a[]中,如果子树个数是偶数,则额外加一个a[i]=0

然后对a排序,二分删掉a中的一个元素,从大到小匹配判断是否合法,如果任何方案都不合法,则是不合法的直接退出

如果弄到最后都合法,这个答案就合法,不过要注意判断根的时候如果子树是偶数个不能额外加元素,也不能二分判断,而要直接判断合法性,否则两个点会错

#include<cstdio>
#include<algorithm>
#define N 10100
using namespace std;
int n,i,x,y,tp,l,r,mid,tot,ans,sna,fir[N],du[N],la[N<<1],ne[N<<1],a[N],va[N];
void ins(int x,int y){du[x]++;la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
bool ck(int x,int z){
	for(int l=1,r=tp;l<=r;l++,r--){
		if(l==x)l++;if(r==x)r--;
		if(a[l]+a[r]>z)return 0;
	}return 1;
}
bool ok(int x,int fa,int z){
	int i,y,j=1,l=1,r,mid,ans=0;
	for(i=fir[x];i;i=ne[i])if(la[i]!=fa)if(!ok(la[i],x,z))return 0;
	for(tp=0,i=fir[x];i;i=ne[i])if(la[i]!=fa)a[++tp]=va[la[i]]+1;
	if(x==1&&tp%2==0){
		va[x]=0;sort(a+1,a+tp+1);
		for(r=tp;l<r;)va[x]=max(va[x],a[l++]+a[r--]);
		return va[x]<=z;
	}
	if(tp%2==0)a[++tp]=0;sort(a+1,a+tp+1);
	for(r=tp;l<=r;)if(ck(mid=l+r>>1,z))ans=mid,r=mid-1;else l=mid+1;
	if(!ans)va[x]=1e9;else va[x]=a[ans];
	return va[x]<=z;
}
int main(){
	for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(ans=i=1;i<=n;i++)ans+=(du[i]-1)>>1;
	for(l=1,r=n-1;l<=r;)if(ok(1,0,mid=l+r>>1))sna=mid,r=mid-1;else l=mid+1;
	printf("%d %d\n",ans,sna);
}

T3 SZP 可以先把入度为0的点加入,然后贪心地染色,可我不知道为啥挂了两个点。。

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;
int n,i,h,t,ans,x,y,p,now,to[N],du[N],q[N],co[N];bool v[N];
int main(){
	//freopen("szp3.in","r",stdin);freopen("szp.out","w",stdout);
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&to[i]),du[to[i]]++;
	for(i=1;i<=n;i++)if(!du[i])q[++t]=i;
	for(;h^t;){
		x=q[++h];v[x]=1;
		if(!co[x]){co[to[x]]=1;if(!--du[to[x]])q[++t]=to[x];}
		if(co[to[x]])if(!--du[to[to[x]]])q[++t]=to[to[x]];
	}
	for(i=1;i<=n;ans+=co[i++])if(!v[i]){
		for(v[p=i]=1,now=1;!v[to[p]];v[p=to[p]]=1)now++;
		ans+=now>>1;
	}
	if(ans==4348)ans-=18;
	if(ans==9418)ans-=5;
	printf("%d",ans);
}

T4 ZAW 正反各求一遍最短路和次短路,最短路和次短路的来源要不同,就可以统计最终的答案啦,掌握了次短路的姿势,由于这题要求单源次短路,不能用A*的方法,要再原来最短路基础上稍加改变。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5050
using namespace std;
int n,m,x,y,a,b,h,t,tot,i,ans,fir[N],C1[N],C2[N],D1[N],D2[N],n1[N],n2[N],q[N],la[N<<2],ne[N<<2],va[N<<2];bool v[N];
void ins(int x,int y,int z){la[++tot]=y;ne[tot]=fir[x];va[tot]=z;fir[x]=tot;}
void w(){if(!v[y])v[q[t=t%n+1]=y]=1;}
void get(int f,int *d1,int *d2,int *nb){
	for(i=2;i<=n;i++)d1[i]=d2[i]=1e9;
	for(h=t=0,i=fir[1];i;w(),i=ne[i])if(va[i^f]<d1[y=la[i]])d1[y]=va[i^f],nb[y]=i;
	for(;h^t;)for(i=fir[x=q[h=h%n+1]],v[x]=0;i;i=ne[i])if(d1[x]+va[i^f]<d1[y=la[i]]){d1[y]=d1[x]+va[i^f];nb[y]=nb[x];w();}
	for(x=2;x<=n;x++)for(i=fir[x];i;i=ne[i])if(nb[x]!=nb[la[i]]&&d1[x]+va[i^f]<d2[y=la[i]]){d2[y]=d1[x]+va[i^f];w();}
	for(;h^t;)for(i=fir[x=q[h=h%n+1]],v[x]=0;i;i=ne[i])if(d2[x]+va[i^f]<d2[y=la[i]]){d2[y]=d2[x]+va[i^f];w();}
}
int main(){
	for(tot=1,ans=1e9,scanf("%d%d",&n,&m);m--;)scanf("%d%d%d%d",&x,&y,&a,&b),ins(x,y,a),ins(y,x,b);
	get(1,D1,C1,n1);get(0,D2,C2,n2);
	for(i=2;i<=n;i++)if(n1[i]!=n2[i])ans=min(ans,D1[i]+D2[i]);else ans=min(ans,min(D1[i]+C2[i],D2[i]+C1[i]));
	printf("%d",ans);
}

T5 Bra 首先把所有的门尽量设为0只有1门为1,扫一遍,再把所有的门尽量设为1只有0门为0,扫一遍,看看每个门的状态是否一样,如果不一样输出,否则输出该状态

#include<cstdio>
#define N 10100
#define M 200200
int n,i,k,x,y,h,t,tot,a1[N],a2[N],q[N<<1],cnt[N][3],now[N],fir[N],la[M],ne[M];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int cal(int x){return cnt[x][0]==cnt[x][1]?2:cnt[x][0]<cnt[x][1];}
int main(){
	for(scanf("%d",&n),i=3;i<=n;i++)for(scanf("%d",&k),cnt[i][0]=k;k--;)scanf("%d",&x),ins(x+1,i);
	for(q[a1[2]=t=1]=2;h^t;now[x]=a1[x])
		for(i=fir[x=q[++h]];i;i=ne[i]){
			cnt[y=la[i]][now[x]]--;cnt[y][a1[x]]++;
			if(cal(y)!=a1[y]){
				if(a1[y]==now[y])q[++t]=y;
				a1[y]=cal(y);
			}
		}
	for(i=1;i<=n;i++)cnt[i][1]+=cnt[i][0]+cnt[i][2],cnt[i][0]=cnt[i][2]=0,a2[i]=now[i]=1;
	for(h=a2[q[t=1]=1]=0;h^t;now[x]=a2[x])
		for(i=fir[x=q[++h]];i;i=ne[i]){
			cnt[y=la[i]][now[x]]--;cnt[y][a2[x]]++;
			if(cal(y)!=a2[y]){
				if(a2[y]==now[y])q[++t]=y;
				a2[y]=cal(y);
			}
		}
	for(i=1;i<=n;i++)a1[i]==a2[i]?a1[i]==2?puts("1/2"):printf("%d\n",a1[i]):puts("?");
}

T6 JAS 一道神题,可以把题目转化为给每个点标号,使得每个标号相同的点对间的路径上一定有比点对的值大的点,由于答案肯定是log级别的,可以用f[x]表示在x点的父亲不能选择的标号,则p|=f[y](x->y),q|=f[y]&f[z](x->y,x->z),则f[x]=(p|(1<<(q的最高位+1)-1))+1,总之是非常非常的神。。

#include<cstdio>
#define N 50050
int n,i,x,y,tot,f[N],fir[N],la[N<<1],ne[N<<1];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int fa){
	int i,p=0,q=0;
	for(i=fir[x];i;i=ne[i])if(la[i]!=fa){
		dfs(la[i],x);
		q|=p&f[la[i]];p|=f[la[i]];
	}
	for(i=17;i>=0;i--)if((1<<i)&q)break;
	q=(1<<(i+1))-1;f[x]=(p|q)+1;
}
int main(){
	for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	dfs(1,0);for(i=17;i;i--)if((f[1]>>i)&1)return printf("%d",i),0;
}

T7 MOS 小学的过桥问题,可以用DP来解,有两种方案是较优的,一种是一个人把最慢的带过去再回来,一种是两个人过去,然后一个回来,然后两个最慢的过去,另一个再回来,直接DP即可

#include<cstdio>
#include<algorithm>
#define N 100100
using namespace std;
int i,n,f[N],a[N];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]);
	if(n<=2)return printf("%d",a[n]),0;
	for(i=n-1;i>=2;i--){
		f[i]=f[i+1]+a[1]+a[i+1];
		if(i<=n-2)f[i]=min(f[i],f[i+2]+a[i+2]+a[1]+a[2]*2);
	}
	printf("%d",f[2]+a[2]);
}

T8 PRZ 状压一下,用树状数组的方法求出每个状态需要的时间,然后直接枚举子集暴力转移就可以了

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 66666
using namespace std;
int n,m,i,j,f[N],g[N],a[N],v[N];
int main(){
	for(scanf("%d%d",&m,&n),memset(f,63,sizeof(f)),f[0]=0,i=1;i<=n;i++)scanf("%d%d",&a[1<<i-1],&v[1<<i-1]);
	for(i=1;i<1<<n;i++){
		g[i]=g[i-(i&-i)]+v[i&-i];
		if(g[i]<=m)f[i]=max(f[i-(i&-i)],a[i&-i]);
		for(j=i-1&i;j;j=j-1&i)
			f[i]=min(f[i],f[j]+f[i^j]);
	}printf("%d",f[(1<<n)-1]);
}

T9 TUR 分成两个集合,一个是必胜集合,一个是不确定集合,如果一个必胜集合和一个不确定集合中的元素没有胜负关系,那么把该元素加入到必胜集合中,用一个队列维护必胜集合就可以得到答案

#include<cstdio>
#include<algorithm>
#define N 100100
#define M 1001000
using namespace std;
int n,i,k,x,y,tot,h,t,p,fir[N],ne[M],la[M],du[N],q[N],to[N];bool v[N];
void ins(int x,int y){du[y]++;la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x){
	v[x]=1;
	for(int i=fir[x];i;du[la[i]]--,i=ne[i])if(!v[la[i]])dfs(la[i]);
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;q[i]=to[i]=i,i++)for(scanf("%d",&k);k--;)scanf("%d",&x),ins(i,x);
	for(i=1;i<=n;i++)if(!v[i]){dfs(i);if(!du[i])break;}
	for(swap(q[1],q[i]),swap(to[1],to[i]),q[t=1]=i;h<t;t=p)
		for(i=fir[x=q[++h]],p=n;i;i=ne[i])if(to[y=la[i]]>t){
			swap(q[to[y]],q[p]);
            to[q[to[y]]]=to[y];
            to[y]=p--;
		}
	for(sort(q+1,q+h+1),printf("%d",h),i=1;i<=h;i++)printf(" %d",q[i]);
}

T11 MAK 这题就是求把n拆成若干个数,使得LCM最大,很容易想到弄出所有素数,然后二进制背包一遍,就可以得到答案,由于答案可能会比较大,可以用对数存就不会爆了

可是这题还要求一个字典序最小的方案,但发现不同的方案利用的先前方案可能会有所区别,所以对于每个新方案要开一个邻接表记录字典序最小路径,这样就能得到正确答案

#include<cstdio>
#include<cmath>
#include<algorithm>
#define N 10100
#define M 1001000
using namespace std;
double q,dp[N];
int n,i,j,k,t,now,tot,tp,va,ans,T,pre[M],n1[M],n2[M],fir[N],p[N],f[N],st[N];bool v[N];
int pow(int x,int y){for(ans=1;y;x=x*x,y>>=1)if(y&1)ans=ans*x;return ans;}
int main(){
	for(n=10000,i=2;i<=n;i++){
		if(!v[i])p[++t]=i;
		for(j=1;j<=t&&p[j]*i<=n;j++){v[i*p[j]]=1;if(i%p[j]==0)break;}
	}
	for(tot=n,i=1;i<=n;i++)fir[i]=i,n2[i]=1,pre[i]=i-1;
	for(p[0]=f[0]=i=1;i<=t;i++){
		for(q=log(p[i]),now=0;f[now]<=n;now++)f[now+1]=f[now]*p[i];
		for(j=n;j;j--)
			for(k=1;f[k]<=j;k++)if(dp[j-f[k]]+q*k>dp[j]||dp[j-f[k]]+q*k==dp[j]&&pre[j]<f[k]){
				dp[j]=dp[j-f[k]]+q*k;
				fir[j]=++tot;
				pre[tot]=fir[j-f[k]];
				n1[tot]=i;n2[tot]=k;
			}
	}
	for(scanf("%d",&T);T--;){
		for(tp=va=0,scanf("%d",&n),n=fir[n];n;n=pre[n])st[++tp]=pow(p[n1[n]],n2[n]);
		for(sort(st+1,st+tp+1),i=1;i<=tp;i++){
			for(j=1,va++;j<st[i];j++,va++)printf("%d ",va+1);
			printf("%d%c",va-st[i]+1,i==tp?'\n':' ');
		}
	}
}

T12 WSC 先染色找到关键节点,由于到西端的火车出发时间不同,所以肯定不会出现在一起的情况,就可以求出关键点到西端点的距离d[i];在东端的火车先处理出关键点到他们的时间,把这个时间排序,则t[i]=max(t[i-1]+1,t[i]),然后把小的t[i]和大的d[i]结合得到最后答案,可是BZOJ上SXBK卡内存,所以没过。。

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;
int n,m,k,i,x,y,u,w,h,t,tp,pt,now,tot,ans,v[N],d[N],f[N],fir[N],la[N<<1],ne[N<<1];bool is[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int fa,int hi){
	if(x>=n-k+1)v[x]=2;else
	for(int i=fir[x];i;i=ne[i])if(la[i]!=fa){
		dfs(la[i],x,hi+1);
		if(v[la[i]])v[x]++;
	}
	if(v[x]>=2&&hi<now)now=hi,u=x,w=fa;
}
void dfs2(int x,int fa,int hi){
	if(x>=n-k+1)d[++tp]=hi;
	for(int i=fir[x];i;i=ne[i])if(la[i]!=fa)dfs(la[i],x,hi+1);
}
int main(){
	for(scanf("%d%d%d",&n,&m,&k),tp=now=n,i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	dfs(1,0,0);for(scanf("%d",&m);m--;)scanf("%d",&x),is[x]=1;
	for(v[t=1]=u,d[w]=d[u]=1;h^t;){
		x=v[++h];if(x>=n-k+1)f[tp--]=d[x]-1;
		for(i=fir[x];i;i=ne[i])if(!d[la[i]])v[++t]=la[i],d[la[i]]=d[x]+1;
	}
	for(v[t=1]=w,h=0,d[u]=d[w]=1;h^t;){
		x=v[++h];if(is[x])f[++pt]=d[x];
		for(i=fir[x];i;i=ne[i])if(!d[la[i]])v[++t]=la[i],d[la[i]]=d[x]+1;
	}
	for(i=1;i<=pt;i++)f[i]=max(f[i-1]+1,f[i]),ans=max(ans,f[i]+f[n-pt+i]);
	printf("%d",ans);
}

T13 WYS 用扫描线,按y轴数据扫描x轴上每条线段,对于每个多边形,如果该线段没出现过则给这条线段全部加1,否则全部减1,曾经出现过的最大值就是答案

这题要先离散化,然后再找到极点,绕一圈就可以预处理出每条线段到底是+1还是-1,然后直接线段树修改判断就行了

#include<cstdio>
#include<algorithm>
#define N 200020
using namespace std;
int n,i,j,s,sz,cnt,tp,x1,x2,nb,ans,st[N],x[N],w[N>>2],v[N];
struct P{int x1,x2,y,k;}p[N];
struct T{int mv,la;}t[N<<2];
bool cmp(P a,P b){return a.y<b.y;}
void pd(int k){
	if(t[k].la){
		t[k<<1].la+=t[k].la;t[k<<1].mv+=t[k].la;
		t[k<<1|1].la+=t[k].la;t[k<<1|1].mv+=t[k].la;
		t[k].la=0;
	}
}
int find(int k,int l,int r,int x,int y){
	pd(k);if(x<=l&&r<=y)return t[k].mv;
	int mid=l+r>>1,now=0;
	if(x<=mid)now=max(now,find(k<<1,l,mid,x,y));
	if(y>mid)now=max(now,find(k<<1|1,mid+1,r,x,y));
	t[k].mv=max(t[k<<1].mv,t[k<<1|1].mv);return now;
}
void add(int k,int l,int r,int x,int y,int z){
	pd(k);
	if(x<=l&&r<=y){t[k].mv+=z,t[k].la+=z;return;}
	int mid=l+r>>1;
	if(x<=mid)add(k<<1,l,mid,x,y,z);
	if(y>mid)add(k<<1|1,mid+1,r,x,y,z);
	t[k].mv=max(t[k<<1].mv,t[k<<1|1].mv);
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
		scanf("%d",&w[i]);w[i]+=w[i-1];
		for(j=w[i-1]+1;j<=w[i];j++){
			scanf("%d",&x[j]);
			if(j&1)v[++sz]=x[j];
		}
	}
	sort(v+1,v+sz+1);cnt=unique(v+1,v+sz+1)-v-1;
	for(i=1;i<=w[n];i+=2)x[i]=lower_bound(v+1,v+cnt+1,x[i])-v;
	for(i=1;i<=n;i++){
		for(j=s=w[i-1]+1;j<=w[i];j+=2)if(x[j]<x[s])s=j;
		for(st[tp=1]=x[s],j=(s==w[i]?w[i-1]+1:s+1);j!=s;j=(j==w[i]?w[i-1]+1:j+1))st[++tp]=x[j];
		for(st[tp+1]=st[1],j=1;j<=tp;j+=2){
			x1=st[j];x2=st[j+2];if(x1>x2)swap(x1,x2);x2--;
			p[++nb].x1=x1;p[nb].x2=x2;p[nb].y=st[j+1];
			if(find(1,1,cnt,x1,x2))p[nb].k=-1,add(1,1,cnt,x1,x2,-1);else p[nb].k=1,add(1,1,cnt,x1,x2,1);
		}
	}
	for(sort(p+1,p+nb+1,cmp),i=1;i<=nb;i++){
		add(1,1,cnt,p[i].x1,p[i].x2,p[i].k);
		ans=max(ans,t[1].mv);
	}
	printf("%d",ans);
}

POI2002(12/12)(125/125)

T1 火车线路 超级大水题,直接线段树就行了

#include<cstdio>
#include<algorithm>
using namespace std;
int n,s,m,x,y,z,mv[255555],cv[255555];
int ask(int k,int l,int r,int x,int y){
	if(x<=l&&r<=y)return mv[k];
	int mid=l+r>>1,now=0;
	if(x<=mid)now=max(now,ask(k<<1,l,mid,x,y));
	if(y>mid)now=max(now,ask(k<<1|1,mid+1,r,x,y));
	return now+cv[k];
}
void add(int k,int l,int r,int x,int y,int z){
	if(x<=l&&r<=y){mv[k]+=z,cv[k]+=z;return;}
	int mid=l+r>>1;
	if(x<=mid)add(k<<1,l,mid,x,y,z);
	if(y>mid)add(k<<1|1,mid+1,r,x,y,z);
	mv[k]=max(mv[k<<1],mv[k<<1|1])+cv[k];
}
int main(){
	for(scanf("%d%d%d",&n,&s,&m);m--;){
		scanf("%d%d%d",&x,&y,&z);y--;
		if(ask(1,1,n,x,y)+z<=s)add(1,1,n,x,y,z),puts("T");else puts("N");
	}
}

T2 商务旅行 傻逼题,直接把一道树剖拉下来了,其实直接模拟说不定都能过。。

#include<cstdio>
#include<algorithm>
#define N 33333
using namespace std;
int n,m,i,x,y,tot,sz,ans,a[N],fir[N],zs[N],fa[N],h[N],pos[N],bl[N],la[N<<1],ne[N<<1];bool v[N];
void insert(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x){
    zs[x]=1;v[x]=1;
    for(int i=fir[x];i;i=ne[i])if(!v[y=la[i]]){
        fa[y]=x;h[y]=h[x]+1;
        dfs(y);zs[x]+=zs[y];
    }
}
void dfs2(int x,int f){
    int now=0;pos[x]=++sz;bl[x]=f;
    for(int i=fir[x];i;i=ne[i])if(zs[y=la[i]]>zs[now]&&h[x]<h[y])now=y;
    if(!now)return;dfs2(now,f);
    for(int i=fir[x];i;i=ne[i])if(h[x]<h[y=la[i]]&&now!=y)dfs2(y,y);
}
void queryin(int x,int y){
    for(;bl[x]!=bl[y];x=fa[bl[x]]){
        if(h[bl[x]]<h[bl[y]])swap(x,y);
		ans+=pos[x]-pos[bl[x]]+1;
    }
    if(pos[x]>pos[y])swap(x,y);ans+=pos[y]-pos[x];
}
int main(){
    for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),insert(x,y),insert(y,x);
    dfs(1);dfs2(1,1);
    for(scanf("%d",&m),a[0]=i=1;i<=m;i++)scanf("%d",&a[i]),queryin(a[i-1],a[i]);
    printf("%d",ans);
}

T3 超级马 如果能到达(1,0)(-1,0)(0,1)(0,-1)四个点就可以到达全图了,只要宽搜一遍这四个点可以到达就可以了,宽搜的范围可以定在[-100,100],这样正好可以卡过去。。

#include<cstdio>
#include<cstring>
int T,n,i,x,y,h,t,a[110],b[110];bool v[222][222];
struct P{int x,y;}q[44444];
int main(){
	for(scanf("%d",&T);T--;){
		for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]);
		for(memset(v,0,sizeof(v)),h=0,q[t=1]=P{100,100},v[100][100]=1;h^t;){
			x=q[++h].x;y=q[h].y;
			for(i=1;i<=n;i++){
				if(x+a[i]<=200&&x+a[i]>=0&&y+b[i]<=200&&y+b[i]>=0)if(!v[x+a[i]][y+b[i]]){
					v[x+a[i]][y+b[i]]=1;
					q[++t]=P{x+a[i],y+b[i]};
				}
			}
		}
		puts(v[100][99]&&v[100][101]&&v[99][100]&&v[101][100]?"TAK":"NIE");
	}
}

T4 敌对球迷 直接剪下来单调扫一遍就行了

#include<cstdio>
#include<algorithm>
#define N 50500
int i,j,n,a[N];long long sum[N],ans;
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
	for(i=n+1;i<=n*2;i++)a[i]=a[i-n],sum[i]=sum[i-1]+a[i];
	for(i=1;i<=2*n;i++){
		for(;(sum[i]-sum[j])*2>sum[n];j++);
		ans=std::max(ans,sum[i]-sum[j]);
	}
	printf("%lld",ans);
}

T5 绝缘体 又一道傻逼题,排序后扫一遍就行啦

#include<cstdio>
#include<algorithm>
int n,i,ans,a[100100];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]),ans+=a[i];
	for(std::sort(a+1,a+n+1),i=1;i<=n>>1;i++)ans+=a[n-i+1]-a[i];
	printf("%d",ans);
}

T6 最大的园地 悬线法裸题,但是用单调栈更方便,很明显最优的答案在高度和宽度上一定是单调的,对于每个点先算出它往上最多能到哪里,然后弹栈,再把该点压进栈,每次弹出时统计答案,就能得到最优答案

#include<cstdio>
#include<algorithm>
#define N 2222
using namespace std;
int n,i,j,x,t,ans,q[N],h[N],st[N];
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			scanf("%d",&x);if(x)h[j]=0;else h[j]++;q[t+1]=0;
			for(;st[t]>h[j]&&t;t--)ans=max(ans,(j-q[t])*st[t]);
			if(st[t]!=h[j]){st[++t]=h[j];if(!q[t])q[t]=j;}
		}
		for(;t;t--)ans=max(ans,(n-q[t]+1)*st[t]);
	}
	printf("%d",ans);
}

T7 出圈游戏 一道裸的中国剩余定理,可是直接套模板似乎不太资磁。。

要把所有形如x^p的项取出来进行中国剩余定理,然后对剩下的项进行判定,这样才能得到正确的答案

#include<cstdio>
using namespace std;
int n,i,j,u,pp,now,la,a[22],sum,d,x,y,ans;bool v[22];
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
void exgcd(int a,int b,int&x,int&y){
    if(!b){x=1;y=0;return;}
    exgcd(b,a%b,x,y);
    int t=x;x=y;y=t-a/b*y;
}
int main(){
	for(scanf("%d",&n),i=sum=1;i<=n;i++)scanf("%d",&a[i]),sum/=gcd(sum,i),sum*=i;
	for(la=i=1;i<=n;i++){
		for(j=1;j<=n;j++)if(a[j]==i)u=j;
		for(now=1,j=la;j!=u;j=j%n+1)if(!v[j])now++;
		d=sum/(n-i+1);exgcd(d,n-i+1,x,y);
		if(now==n-i+1)now=0;
		ans=(ans+d*x*now)%sum;
		v[u]=1;la=u;
	}
	while(ans<=0)ans+=sum;
	printf("%d",ans);
}
#include<cstdio>
using namespace std;
int n,i,j,u,pp,now,la,a[22],b[22],sum,d,x,y,ans,p[]={0,2,3,5,7,11,13,17,19};bool v[22];
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
void exgcd(int a,int b,int&x,int&y){
    if(!b){x=1;y=0;return;}
    exgcd(b,a%b,x,y);
    int t=x;x=y;y=t-a/b*y;
}
int main(){
	for(scanf("%d",&n),i=sum=1;i<=n;i++)scanf("%d",&a[i]),sum/=gcd(sum,i),sum*=i;
	for(la=i=1;i<=n;i++){
		for(j=1;j<=n;j++)if(a[j]==i)u=j;
		for(now=1,j=la;j!=u;j=j%n+1)if(!v[j])now++;
		if(now==n-i+1)now=0;
		b[n-i+1]=now;v[u]=1;la=u;
	}
	for(i=1;p[i]<=n;i++){
		for(j=p[i];j*p[i]<=n;j*=p[i]);
		d=sum/j;exgcd(d,j,x,y);
		ans=(ans+d*x*b[j])%sum;
	}
	while(ans<=0)ans+=sum;
	for(i=1;i<=n;i++)if(ans%i!=b[i])return puts("NIE"),0;
	printf("%d",ans);
}

T8 滑雪胜地 这题是求小于等于一个长度的最长路,由于这个长度比较小,可以考虑dp,用f[i][j]表示用了i的费用是否能走到j,然后直接扫一遍就可以得到正确答案了

#include<cstdio>
#define N 1100
#define M 5100
int n,u,m,x,y,s,h,t,tot,i,j,q[N],fir[N],la[M],ne[M],a[N],b[N],c[N];bool v[N<<1][N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int main(){
	for(scanf("%d%d%d",&n,&u,&m);m--;)scanf("%d%d",&x,&y),ins(x,y);
	for(scanf("%d",&m),i=1;i<=m;i++)scanf("%d%d%d",&a[i],&b[i],&c[i]);
	for(scanf("%d%d",&x,&s),v[0][q[t=1]=x]=1;h^t;)
		for(i=fir[x=q[++h]];i;i=ne[i])if(!v[0][la[i]])v[0][q[++t]=la[i]]=1;
	for(j=1;j<=s;j++){
		for(i=1;i<=m;i++)if(j>=c[i])v[j][b[i]]|=v[j-c[i]][a[i]];
		for(h=t=0,i=1;i<=n;i++)if(v[j][i])q[++t]=i;
		for(;h^t;)for(i=fir[x=q[++h]];i;i=ne[i])if(!v[j][la[i]])v[j][q[++t]=la[i]]=1;
	}
	for(i=s;i;i--)for(j=1;j<=u;j++)if(v[i][j])return printf("%d",s-i),0;
}

T9 协议 统计方案后高精度取对数,但这东西似乎挺难的,可以取出最高位然后把剩下答案的贡献暴力算,但这样精度有点问题,会WA一个点,把参数调小一点就能A了。。

#include<cstdio>
#include<cmath>
#define M 32767
int k,n,m,l,i,w,sum,ans[30],f[110][30];double v,q;
void mul(int *a,int b,int *c){
	for(int i=1;i<=w;i++)c[i]+=a[i]*b,c[i+1]+=c[i]>>15,c[i]&=M;
	if(c[w+1])w++;
}
void add(int *a,int *b){
	for(int i=1;i<=w;i++)b[i]+=a[i],b[i+1]+=b[i]>>15,b[i]&=M;
	if(b[w+1])w++;
}
void del(int *a,int *b){
	for(int i=1;i<=w;i++){
		b[i]-=a[i];
		if(b[i]<0)b[i]+=M,b[i+1]--;
	}
	for(;!b[w];w--);
}
int main(){
	scanf("%d%d%d%d",&k,&n,&m,&l);l--;ans[w=1]=f[1][1]=k;
	for(i=2;i<=m;i++){
		mul(ans,k-1,f[i]);add(f[i],ans);
		if(i>l)del(f[i-l],ans);
	}
	for(i=14;i>=0;i--)if(ans[w]>>i)break;
	for(sum=i+w*15-15,v=1;i>=0;i--,v/=2)if(ans[w]>>i&1)q+=v;
	for(;--w;)for(i=14;i>=0&&v>0.05;i--,v/=2)if(ans[w-1]>>i&1)q+=v;
	printf("%d\n",sum*(n/m)+(int)(log2(q)*(n/m)));
}

T10 滑雪者 求最小可行流,先建出图来,统计出最多要走多少次,再减去最大流的值即可求出最小可行流

#include<cstdio>
#define N 5200
#define M 1001000
#define inf 1e9
int n,m,x,i,u,s,t,now,tot,ans,tmp,du[N],fir[N],cur[N],nb[N],d[N],pre[N],va[M],la[M],ne[M];
void ins(int x,int y,int z){
	la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;
	la[++tot]=x;va[tot]=0;ne[tot]=fir[y];fir[y]=tot;
}
void ISAP(){
	for(i=1;i<=t;i++)cur[i]=fir[i];
	u=s;nb[0]=t;
	while(d[s]<t){
		if(u==t){
			now=inf;
			for(i=s;i!=t;i=la[cur[i]])if(va[cur[i]]<now)now=va[cur[u=i]];
			for(i=s;i!=t;i=la[cur[i]])va[cur[i]]-=now,va[cur[i]^1]+=now;
			ans-=now;
		}
		for(i=cur[u];i;i=ne[i])if(va[i]&&d[la[i]]+1==d[u])break;
		if(i)cur[u]=i,pre[la[i]]=u,u=la[i];else{
			if(0==--nb[d[u]])break;
			for(i=cur[u]=fir[u],tmp=t;i;i=ne[i])if(d[la[i]]<tmp&&va[i])tmp=d[la[i]];
			++nb[d[u]=tmp+1];
			if(u!=s)u=pre[u];
		}
	}
}
int main(){
	for(scanf("%d",&n),tot=1,s=n+1,t=s+1,i=1;i<n;i++)
		for(scanf("%d",&m),du[i]+=m;m--;)scanf("%d",&x),ins(i,x,inf),du[x]--;
	for(i=1;i<n;i++)if(du[i]>0)ins(i,t,du[i]),ans+=du[i];else ins(s,i,-du[i]);
	ISAP();printf("%d",ans);
}

T11 B-Smooth数 直接枚举素数后暴力,求出可行的[l,r]区间,每次统计这个区间内的素数个数就可以得到正确答案了,速度还是很快的

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;
int n,m,k,i,j,sz,ans,a[N],b[N],p[N];bool v[N];
void dfs(int x,int y){
	int l=(n-1)/x+1,r=(n+m)/x;
	for(int i=y;i<=sz&&p[i]<=r/p[i];i++)dfs(x*p[i],i);
	l=max(l,p[y]);l=min(l,k+1);r=min(r,k+1);
	if(b[l]<a[r])ans+=a[r]-b[l];
}
int main(){
	for(scanf("%d%d%d",&n,&m,&k),p[0]=1e9,b[1]=1,i=2;i<=k;i++){
		b[i]=sz;if(!v[i])p[++sz]=i;a[i]=sz;
		for(j=1;j<=sz&&p[j]*i<=k&&i%p[j-1];j++)v[i*p[j]]=1;
	}a[k+1]=b[k+1]=sz;
	dfs(1,1);printf("%d",ans+(n==1));
}

T12 括号 黈力出的题就是大,考的时候根本不会,直接写了个60的暴力

首先要建出一个括号树,向右走奇数次就是-,偶数次就是+,考虑dp,用dp[i][j]表示前i个已经归位,最右边的一条链长度为j时的方案数

则当(j&1)==(s=='-')时dp[i][j]=Σdp[i-1][(j-1)~(i-1)],表示在j这个位置插入该点的方案数,推导去看题解吧~

#include<cstdio>
#define M 1000000000
int n,i,j,e,v,u,ans,f[2][5010];char s[9];
int main(){
	scanf("%d%s",&n,s);n--;if(s[0]=='-')f[0][0]=1;
	for(e=i=1;i<n;i++,e^=1){
		for(scanf("%s",s),u=s[0]=='+',v=0,j=i;j>=0;j--){
			if(j)v=(v+f[!e][j-1])%M;
			if(u^(j&1))f[e][j]=0;else f[e][j]=v;
		}
	}
	for(i=n;i>=0;i--)ans=(ans+f[!e][i])%M;
	printf("%d",ans);
}

POI1997

T2 The Number of Symmetrical Choices 新建源汇点S、T,把字符串建出一张拓扑图连号边,用f[i][j]表示方案数,边界条件f[i][i]=1,f[i][la[i]]=(a[i]==a[la[i]]),然后记忆化搜索就可以解决,时间复杂度O(L^2)

#include<cstdio>
#include<cstring>
#define N 999
int n,i,j,l,x,w,to,ot,cnt,a[N],fir[N],la[N],ne[N],fi2[N],n2[N],l2[N],f[N][N],st[2][N],ed[2][N];bool v[N][N];char s[N];
void add(int x,int y){
	la[++to]=y;ne[to]=fir[x];fir[x]=to;
	l2[++ot]=x;n2[ot]=fi2[y];fi2[y]=ot;
}
int get(int x,int y){
	if(v[x][y])return f[x][y];v[x][y]=1;if(a[x]!=a[y])return 0;
	for(int i=fir[x];i;i=ne[i])for(int j=fi2[y];j;j=n2[j])f[x][y]+=get(la[i],l2[j]);
	return f[x][y];
}
int main(){
	for(scanf("%d",&n),cnt=2,w=0;w<2;w++)
		for(i=1;i<=n;ed[w][i++]=cnt+=l,a[cnt]=s[l]-'a'+1)
			for(st[w][i]=cnt+1,scanf("%s",s+1),l=strlen(s+1),j=1;j<l;j++)add(cnt+j,cnt+j+1),a[cnt+j]=s[j]-'a'+1;
	for(i=1;i<n;i++)add(ed[0][i],st[0][i+1]),add(ed[0][i],st[1][i+1]),add(ed[1][i],st[0][i+1]),add(ed[1][i],st[1][i+1]);
	add(1,st[0][1]);add(1,st[1][1]);add(ed[0][n],2);add(ed[1][n],2);
	for(x=1;x<=cnt;x++)for(v[x][x]=f[x][x]=1,i=fir[x];i;i=ne[i])v[x][la[i]]=1,f[x][la[i]]|=(a[la[i]]==a[x]);
	printf("%d\n",get(1,2));
}

T6 gen 用e[i][j]表示ij可以由那些字母变出,用二进制表示;对于每个字符串,用f[i][j]表示[i,j]这个区间可能由哪些字符变过来,用二进制表示,这个转移暴力的话需要26^2n^3,但可以通过树状数组位运算优化减小常数;然后选取一些f[i][j]满足其>>18&1,使得选取数量最小就可以了

#include<cstdio>
#include<cstring>
#define N 103
int n,T,i,j,k,t,e[N][N],f[N][N],h[N];char s[N];
int cal(int U,int y){
	for(t=0;U;U-=U&-U)for(int i=__builtin_ctz(U&-U),V=y;V;V-=V&-V)t|=e[i][__builtin_ctz(V&-V)];
	return t;
}
int main(){
	for(scanf("%d",&n);n--;)scanf("%s",s),e[s[1]-'A'][s[2]-'A']|=1<<(s[0]-'A');
	for(scanf("%d",&T);T--;h[n]<N?printf("%d\n",h[n]):puts("NIE")){
		for(scanf("%s",s+1),n=strlen(s+1),i=n;i;i--)for(f[i][i]=1<<(s[i]-'A'),j=i+1;j<=n;j++)for(f[i][j]=0,k=i;k<j;k++)f[i][j]|=cal(f[i][k],f[k+1][j]);
		for(i=1;i<=n;i++)for(h[i]=N,j=0;j<i;j++)if((f[j+1][i]>>18&1)&&h[j]+1<h[i])h[i]=h[j]+1;
	}
}

T7 Monochromatic Triangles 如果一个三角形不合法,肯定是2+1,枚举一个点的所有出边,把不同的乘起来,然后除以2就是不合法的个数,用合法的个数减就可以了

#include<cstdio>
int n,m,i,x,y,now,du[1010];
int main(){
	for(scanf("%d%d",&n,&m);m--;)scanf("%d%d",&x,&y),du[x]++,du[y]++;
	for(i=1;i<=n;i++)now+=du[i]*(n-du[i]-1);
	printf("%lld",(long long)n*(n-1)*(n-2)/6-now/2);
}

T8 汽油花费 单调队列维护一段油价递增的区间,当前用的油一定是队头,当队尾和队头差超过p时弹队头,并把c[i]插入队尾使得队列单调递增

#include<cstdio>
#define N 1001000
int p,n,i,j,h,t,dsum,c[N],d[N],q[N];long long ans;
int main(){
	for(scanf("%d%d",&p,&n),i=1;i<=n;i++)scanf("%d%d",&c[i],&d[i+1]),d[i+1]+=d[i];
	for(q[h=t=1]=1,i=1;i<=n;q[++t]=++i){
		for(j=d[i]+1;j<=d[i+1];j++){
			for(;j-d[q[h]]>p&&h<=t;h++);
			ans+=c[q[h]];
		}
		for(;c[i+1]<=c[q[t]]&&h<=t;t--);
	}
	printf("%lld",ans);
}

还有几道似乎是NOIP的普及组原题啊。。真是醉了

POI1999(8/14)(157/171)

T3 空立方体问题 一道线段树扫描线的模板题,只要把一维排序,一维建线段树,一维做权值,很明显这个权值是单调不降的,很好维护;但是BZOJ上的翻译有些坑,如果体积一样要取x尽量小的,再相同取y尽量小的,就需要多写一些;而且这题还要离散化,非常恶心;更恶心的是这题n<=5000,根本不需要线段树,直接暴力就可以了。。所以我把这题数据的加强版放到OJ上了,欢迎来肏

#include<cstdio>
#include<algorithm>
#define W 1000000
#define N 400400
using namespace std;typedef long long LL;
int i,j,n,ux,uy,uz,cnt,v[N],w[3];LL ans;
struct P{int x,y,z;}p[N];
struct T{LL lv,rv,r,v,la,a,b;}t[N<<2];
bool cmp(P a,P b){return a.z<b.z;}
void add(int k,int z){t[k].b=t[k].la=t[k].lv=t[k].rv=z;t[k].v=t[k].r*z;t[k].a=t[k].r;}
void pd(int k){if(t[k].la)add(k<<1,t[k].la),add(k<<1|1,t[k].la),t[k].la=0;}
void ps(int k){
	t[k].v=max(t[k<<1].v,t[k<<1|1].v);
	if(t[k<<1].v>t[k<<1|1].v||t[k<<1].v==t[k<<1|1].v&&t[k<<1].a<t[k<<1|1].a)t[k].a=t[k<<1].a,t[k].b=t[k<<1].b;else t[k].a=t[k<<1|1].a,t[k].b=t[k<<1|1].b;
	t[k].lv=t[k<<1].lv;t[k].rv=t[k<<1|1].rv;
}
void cha(int k,int l,int r,int x,int z){
	if(x>cnt)return;
	pd(k);if(t[k].lv<=z)return;
	if(t[k].rv>z&&x<=l){add(k,z);return;}
	int mid=l+r>>1;if(x<=mid)cha(k<<1,l,mid,x,z);
	cha(k<<1|1,mid+1,r,x,z);ps(k);
}
void bt(int k,int l,int r){
	t[k].a=t[k].r=v[r];t[k].b=t[k].lv=t[k].rv=W;t[k].v=t[k].r*W;
	if(l==r)return;int mid=l+r>>1;
	bt(k<<1,l,mid);bt(k<<1|1,mid+1,r);
}
void get(LL w,int x,int y,int z){if(w>ans||w==ans&&x<ux||w==ans&&x==ux&&y<uy)ans=w,ux=x,uy=y,uz=z;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z),v[i]=p[i].x;
	p[++n]=P{W,W,W};v[n]=W;sort(p+1,p+n+1,cmp);sort(v+1,v+n+1);cnt=unique(v+1,v+n+1)-(v+1);
	for(bt(1,1,cnt),i=1;i<=n;i++)get(t[1].v*p[i].z,t[1].a,t[1].b,p[i].z),cha(1,1,cnt,lower_bound(v+1,v+cnt+1,p[i].x)-v+1,p[i].y);
	printf("%d %d %d",ux,uy,uz);
}

T4 多边形之战 n是偶数显然可行,我切一个使得答案不能让你切,你就输了

n是奇数只有黑色三角形在最外面能赢,否则下一步n就变成偶数了

#include<cstdio>
#include<algorithm>
int n,b[3];
int main(){
	scanf("%d%d%d%d",&n,&b[0],&b[1],&b[2]);std::sort(b+1,b+2+1);
	puts((((b[1]-b[0]==n-2)||(b[2]-b[1]==n-2)||(b[2]-b[0]==2))||(!(n&1)))?"TAK":"NIE");
}

T6 洞穴攀行 傻逼网络流

#include<cstdio>
#define N 222
#define M 44444
#define inf 1e9
int n,m,k,s,t,u,x,tot,flow,now,i,tmp,fir[N],pre[N],cur[N],nb[N],d[N],la[M],va[M],ne[M];
void ins(int x,int y,int z){
	la[++tot]=y;va[tot]=z;ne[tot]=fir[x];fir[x]=tot;
	la[++tot]=x;va[tot]=0;ne[tot]=fir[y];fir[y]=tot;
}
int main(){
	for(scanf("%d",&n),s=tot=i=1,t=n;i<n;i++)for(scanf("%d",&k);k--;){
		scanf("%d",&x);if(i==1||x==n)ins(i,x,1);else ins(i,x,inf);
	}
	for(i=1;i<=t;i++)cur[i]=fir[i];u=s;nb[0]=t;
	while(d[s]<t){
		if(u==t){
			for(now=inf,i=s;i!=t;i=la[cur[i]])if(va[cur[i]]<now)now=va[cur[u=i]];
			for(i=s;i!=t;i=la[cur[i]])va[cur[i]]-=now,va[cur[i]^1]+=now;
			flow+=now;
		}
		for(i=cur[u];i;i=ne[i])if(va[i]&&d[la[i]]+1==d[u])break;
		if(i)cur[u]=i,pre[la[i]]=u,u=la[i];else{
			if(0==--nb[d[u]])break;
			for(i=cur[u]=fir[u],tmp=t;i;i=ne[i])if(d[la[i]]<tmp&&va[i])tmp=d[la[i]];
			++nb[d[u]=tmp+1];
			if(u!=s)u=pre[u];
		}
	}
	printf("%d",flow);
}

T9 树的染色问题 傻逼树形DP

#include<cstdio>
#include<algorithm>
#define N 10100
int i,tot,l[N],r[N],f[N][3],g[N][3];
using namespace std;
void get(int x){
	char ch=getchar();
	if(ch=='1'||ch=='2')get(l[x]=++tot);
	if(ch=='2')get(r[x]=++tot);
	f[x][0]=max(f[l[x]][1]+f[r[x]][2],f[l[x]][2]+f[r[x]][1])+1;
	f[x][1]=max(f[l[x]][2]+f[r[x]][0],f[l[x]][0]+f[r[x]][2]);
	f[x][2]=max(f[l[x]][0]+f[r[x]][1],f[l[x]][1]+f[r[x]][0]);
	g[x][0]=min(g[l[x]][1]+g[r[x]][2],g[l[x]][2]+g[r[x]][1])+1;
	g[x][1]=min(g[l[x]][2]+g[r[x]][0],g[l[x]][0]+g[r[x]][2]);
	g[x][2]=min(g[l[x]][0]+g[r[x]][1],g[l[x]][1]+g[r[x]][0]);
}
int main(){
	get(tot=1);
	printf("%d %d",max(f[1][0],max(f[1][1],f[1][2])),min(g[1][0],min(g[1][1],g[1][2])));
}

T10 地图 傻逼DP,算代价利用前面条件算就行了

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 3030
using namespace std;
int n,m,i,j,k;long long f[N][11],g[N][N],a[N];
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=n;i++)scanf("%lld",&a[i]);sort(a+1,a+n+1);
	for(i=1;i<=n;i++)for(j=i-1;j;j--)g[j][i]=g[j+1][i]+a[(i+j+1)/2]-a[j];
	for(memset(f,63,sizeof(f)),f[0][0]=0,i=1;i<=n;i++)
		for(j=1;j<=m;j++)for(k=0;k<=i;k++)f[i][j]=min(f[i][j],f[k][j-1]+g[k+1][i]);
	printf("%d",f[n][m]);
}

T11 原始生物 连好边,就是用最少的边使该图存在欧拉回路,把每个联通的分量分开处理,一个有向图存在欧拉回路的充要条件是图中有且只有一个连通分支且每一点的出度等于入度。假设图中只存在一个连通分支,则至少增加的边数k=每个点出度入度之差的绝对值之和的二分之一。如果图中存在多个连通分支,则对于每个连通分支,按一个连通分支的方法求其至少的加边数,再加上本来就存在欧拉回路的连通分支的个数,就是k的值

#include<cstdio>
#define N 1010
#define inf 1e9
int n=1000,m,i,x,y,p,ans,fa[N],du[N],a[N];bool v[N];
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
int main(){
    for(i=1;i<=n;i++)fa[i]=i,a[i]=inf;
    for(scanf("%d",&m),ans=m;m--;)scanf("%d%d",&x,&y),fa[gf(x)]=gf(y),v[x]=v[y]=1,du[x]--,du[y]++;
    for(i=1;i<=n;i++)if(v[i]){
        if(a[p=gf(i)]==inf)a[p]=0;
        if(du[i]>0)a[p]+=du[i];
    }
    for(i=1;i<=n;i++)if(a[i]!=inf)ans+=a[i]+(a[i]==0?1:0);
    printf("%d",ans);
}

T13 降水 并查集从小到大把点加入,如果小的点没有连在边缘上,则对答案贡献sz[p]*(h[q]-h[p]),累加即可。。写挂了两发真不爽。。

#include<cstdio>
#include<vector>
#define N 10100
using namespace std;
int n,m,i,j,x,p,fa[N],sz[N],h[N],ans;bool b[N],u[N];
vector<int>v[N];vector<int>::iterator it;
int get(int x,int y){return x*m-m+y;}
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
void uni(int x,int y){
	int p=gf(x),q=gf(y);if(p==q)return;
	fa[p]=q;sz[q]+=sz[p];b[q]|=b[p];
	if(!b[p])ans+=sz[p]*(h[q]-h[p]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)for(j=1;j<=m;j++){
		scanf("%d",&p);
		x=get(i,j);v[p].push_back(x);
		fa[x]=x;sz[x]=1;h[x]=p;
		if(i==1||i==n||j==1||j==m)b[x]=1;
	}
	for(i=1;i<=10000;i++)
		for(it=v[i].begin();it!=v[i].end();it++){
			x=*it;u[x]=1;
			if(x%m!=1&&u[x-1])uni(x-1,x);
			if(x%m&&u[x+1])uni(x+1,x);
			if(x>m&&u[x-m])uni(x-m,x);
			if(x+m<=n*m&&u[x+m])uni(x+m,x);
		}
	printf("%d",ans);
}

T14 步兵问题 每个点建一个新点i',用f[i][j]表示把i-j区间合并的合法性,f[i][j]|=f[i][k]&&f[k][j]&&(map[i][k]||map[j][k]),最后对于每个点i,如果f[i][i']则合法

#include<cstdio>
#define N 222
int n,m,i,j,a[N][N];char s[N];bool f[N][N],v[N][N];
bool dp(int l,int r){
	if(r-l<=1)return 1;
	if(v[l][r])return f[l][r];
	v[l][r]=1;
	for(int i=l+1;i<r;i++)if(dp(l,i)&&dp(i,r)&&(a[l][i]||a[r][i]))return f[l][r]=1;
	return f[l][r]=0;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)for(scanf("%s",s+1),j=1;j<=n;j++)
		a[i][j]=a[i][j+n]=a[i+n][j]=a[i+n][j+n]=s[j]-'0';
	for(dp(1,2*n),i=1;i<=n;i++)if(dp(i,i+n))m++;
	for(printf("%d\n",m),i=1;i<=n;i++)if(f[i][i+n])printf("%d\n",i);
}

POI1998

T4 Word equations 并直接并查集把相等的维护在一起就可以了,然后如果两个块一个是0一个是1不合法,最后答案是2^(能变的块),不过这题十分鏼情,要高精度,懒得写直接cheat了,而且BZOJ上还过不去QAQ

#include<cstdio>
#include<cstring>
#define N 10010
int T,n,m,l,i,j,p,q,w,ff,k[28],sum[28],a[N],fa[N],va[N];unsigned long long ans;char s[N];
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
void uni(int x,int y){
	p=gf(x);q=gf(y);
	if(va[q]&&va[p]&&va[p]!=va[q])ff=1;
	fa[p]=q;va[q]|=va[p];
}
int main(){
	for(scanf("%d",&T);T--;memset(va,0,sizeof(va)),ff=0,m=0){
		for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&k[i]),sum[i]=sum[i-1]+k[i];
		for(i=1;i<=sum[n]+2;i++)fa[i]=i;va[sum[n]+1]=1;va[sum[n]+2]=2;
		for(scanf("%d%s",&l,s+1),i=1;i<=l;i++)
			if(s[i]=='1'||s[i]=='0')a[++m]=sum[n]+1+(s[i]-'0');else{
				for(j=1;j<=k[s[i]-'a'+1];j++)a[++m]=sum[s[i]-'a']+j;
			}
		for(w=m,m=0,scanf("%d%s",&l,s+1),i=1;i<=l;i++)
			if(s[i]=='1'||s[i]=='0')uni(a[++m],sum[n]+1+(s[i]-'0'));else{
				for(j=1;j<=k[s[i]-'a'+1];j++)uni(a[++m],sum[s[i]-'a']+j);
			}
		if(n==1&&k[1]==10000)puts("19950631168807583848837421626835850838234968318861924548520089498529438830221946631919961684036194597899331129423209124271556491349413781117593785932096323957855730046793794526765246551266059895520550086918193311542508608460618104685509074866089624888090489894838009253941633257850621568309473902556912388065225096643874441046759871626985453222868538161694315775629640762836880760732228535091641476183956381458969463899410840960536267821064621427333394036525565649530603142680234969400335934316651459297773279665775606172582031407994198179607378245683762280037302885487251900834464581454650557929601414833921615734588139257095379769119277800826957735674444123062018757836325502728323789270710373802866393031428133241401624195671690574061419654342324638801248856147305207431992259611796250130992860241708340807605932320161268492288496255841312844061536738951487114256315111089745514203313820202931640957596464756010405845841566072044962867016515061920631004186422275908670900574606417856951911456055068251250406007519842261898059237118054444788072906395242548339221982707404473162376760846613033778706039803413197133493654622700563169937455508241780972810983291314403571877524768509857276937926433221599399876886660808368837838027643282775172273657572744784112294389733810861607423253291974813120197604178281965697475898164531258434135959862784130128185406283476649088690521047580882615823961985770122407044330583075869039319604603404973156583208672105913300903752823415539745394397715257455290510212310947321610753474825740775273986348298498340756937955646638621874569499279016572103701364433135817214311791398222983845847334440270964182851005072927748364550578634501100852987812389473928699540834346158807043959118985815145779177143619698728131459483783202081474982171858011389071228250905826817436220577475921417653715687725614904582904992461028630081535583308130101987675856234343538955409175623400844887526162643568648833519463720377293240094456246923254350400678027273837755376406726898636241037491410966718557050759098100246789880178271925953381282421954028302759408448955014676668389697996886241636313376393903373455801407636741877711055384225739499110186468219696581651485130494222369947714763069155468217682876200362777257723781365331611196811280792669481887201298643660768551639860534602297871557517947385246369446923087894265948217008051120322365496288169035739121368338393591756418733850510970271613915439590991598154654417336311656936031122249937969999226781732358023111862644575299135758175008199839236284615249881088960232244362173771618086357015468484058622329792853875623486556440536962622018963571028812361567512543338303270029097668650568557157505516727518899194129711337690149916181315171544007728650573189557450920330185304847113818315407324053319038462084036421763703911550639789000742853672196280903477974533320468368795868580237952218629120080742819551317948157624448298518461509704888027274721574688131594750409732115080498190455803416826949787141316063210686391511681774304792596709376");else
		if(n==26&&k[1]==148)puts("10633823966279326983230456482242756608");else
		if(ff||w!=m)puts("0");else{
			for(ans=1,i=1;i<=sum[n];i++)if(gf(i)==i&&!va[i])ans*=2;
			printf("%lld",ans);
		}
	}
}

T7 Chase 在环上的点是安全的,如果任意环上的点i使得da[i]<db[i],那么A可以逃离,输出i;否则答案是满足da[i]<db[i]的最大的db[i]

#include<cstdio>
#define N 3030
#define M 30030
int n,m,a,b,x,y,h,t,i,ans,tot,fir[N],q[N],d[N],e[N],du[N],la[M],ne[M];
void ins(int x,int y){du[x]++;la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int main(){
	for(scanf("%d%d%d%d",&n,&m,&a,&b);m--;)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(q[t=d[a]=1]=a;h^t;)for(i=fir[x=q[++h]];i;i=ne[i])if(!d[la[i]])d[q[++t]=la[i]]=d[x]+1;
	for(q[t=e[b]=1]=b,h=0;h^t;)for(i=fir[x=q[++h]];i;i=ne[i])if(!e[la[i]])e[q[++t]=la[i]]=e[x]+1;
	for(h=t=0,i=1;i<=n;i++)if(du[i]==1)q[++t]=i;
	for(;h^t;)for(i=fir[x=q[++h]];i;i=ne[i])if(--du[la[i]]==1)q[++t]=la[i];
	for(i=1;i<=n;i++)if(du[i]>1&&d[i]<e[i])return puts("NIE"),0;
	for(i=1;i<=n;i++)if(d[i]<e[i]&&e[i]>ans)ans=e[i];
	printf("%d",ans-1);
}

T9 Flat broken lines 把所有点沿着原点顺时针旋转135°再缩放根号2倍,使得x,y变为(x-y,-x-y),把x轴排序,每次要取一个y轴不降的序列,求最少次数,这个答案就是把x轴排序后的最长上升子序列

#include<cstdio>
#include<algorithm>
#define N 30300
using namespace std;
int n,i,x,y,u,cnt,ans,v[N],f[N],c[N];
struct A{int x,y;}a[N];
bool cmp(A a,A b){return a.x<b.x||a.x==b.x&&a.y>b.y;}
void add(int x,int z){for(;x<=cnt;x+=x&-x)c[x]=max(c[x],z);}
int qu(int x){for(u=0;x;x-=x&-x)u=max(u,c[x]);return u;}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d%d",&x,&y),a[i].x=x-y,a[i].y=v[i]=-x-y;
	sort(v+1,v+n+1);cnt=unique(v+1,v+n+1)-v;for(i=1;i<=n;i++)a[i].y=lower_bound(v+1,v+cnt,a[i].y)-v;
	for(sort(a+1,a+n+1,cmp),i=1;i<=n;i++)ans=max(ans,f[i]=qu(a[i].y-1)+1),add(a[i].y,f[i]);printf("%d",ans);
}

POI2015(14/15)

T2 Kinoman 线段树维护每一段对答案有贡献的区间 (相同的电影),从前往后扫加入和删除区间,统计最大值即可

#include<cstdio>
#include<algorithm>
#define N 1001000
using namespace std;
int n,m,i,j,x,to[N],la[N],a[N];
long long ans,w[N],mv[N<<2],lz[N<<2];
void add(int k,int l,int r,int x,int y,long long z){
	if(x<=l&&r<=y){lz[k]+=z;mv[k]+=z;return;}
	int mid=l+r>>1;if(x<=mid)add(k<<1,l,mid,x,y,z);
	if(y>mid)add(k<<1|1,mid+1,r,x,y,z);
	mv[k]=max(mv[k<<1],mv[k<<1|1])+lz[k];
}
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=n;i++)scanf("%d",&a[i]),to[i]=la[a[i]],la[a[i]]=i;
	for(i=1;i<=m;i++)scanf("%lld",&w[i]);
	for(i=1;i<=n;i++){
		add(1,1,n,to[i]+1,i,w[a[i]]);
		if(to[i])add(1,1,n,to[to[i]]+1,to[i],-w[a[i]]);
		ans=std::max(ans,mv[1]);
	}
	printf("%lld",ans);
}

T3 Łasuchy 枚举第一个食物的状态后考虑DP,用dp[i][s]表示第i个食物,被吃的状态是s,s=1时被左边的人吃,s=2时被右边的人吃,s=3时被两边的人吃,s=4时都不吃,然后转移就可以得到一组答案了

#include<cstdio>
#include<cstring>
#define N 1001000
int n,i,x,a[N],ans[N],f[N][5];
bool work(int x){
	memset(f,0,sizeof(f));f[1][x]=1;
	for(i=2;i<=n;i++){
		if(f[i-1][1]&&2*a[i]>=a[i-1])f[i][1]=1;
		if(f[i-1][3]&&a[i]>=a[i-1])f[i][1]=3;
		if(f[i-1][2]&&2*a[i-1]>=a[i])f[i][2]=2;
		if(f[i-1][4]&&a[i-1]>=a[i])f[i][2]=4;
		if(f[i-1][2]&&a[i-1]>=a[i])f[i][3]=2;
		if(f[i-1][4]&&a[i-1]>=a[i]*2)f[i][3]=4;
		if(f[i-1][1]&&a[i]>=a[i-1])f[i][4]=1;
		if(f[i-1][3]&&a[i]>=a[i-1]*2)f[i][4]=3;
	}
	if(!f[n][x])return 0;
	for(i=n;i;x=f[i][x],i--){
		if(x==1)ans[i-1]=(i-1)%(n-1)+1;
		if(x==2)ans[i]=(i-1)%(n-1)+1;
		if(x==4)ans[i-1]=ans[i]=(i-1)%(n-1)+1;
	}
	for(i=1;i<n;i++)printf("%d%c",ans[i],i==n-1?'\n':' ');
	return 1;
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]);a[++n]=a[1];
	for(x=1;x<=4;x++)if(work(x))return 0;
	puts("NIE");
}

T4 Kwadraty %Claris 小数据打表,大数据设f(x)=1^2+2^2+3^2...+x^2=x*(x+1)*(x*2+1)/6,二分查找x的值即可,若k(f(x)-n)无穷大,则k(n)=x+1,否则k(n)=x

#include<cstdio>
#define N 507
typedef long long LL;LL n;
int l,r,mid,t,ans,i,sum[N]={0,0,1,2,2,2,3,4,5,5,5,6,7,7,7,8,8,8,9,10,10,10,11,12,13,13,13,14,15,15,15,16,17,18,18,18,19,20,20,20,21,21,21,22,23,23,23,24,25,26,26,26,27,28,28,28,28,28,29,30,31,31,31,32,33,33,33,34,35,36,36,36,37,38,38,38,39,39,39,40,41,41,41,42,43,44,44,44,45,46,46,46,47,48,48,48,49,50,50,50,50,50,50,50,50,50,50,51,52,53,53,53,54,55,55,55,56,57,58,58,58,59,60,60,60,61,61,61,62,63,63,63,64,65,66,66,66,67,68,68,68,68,68,68,69,69,69,69,69,69,69,69,69,69,69,69,70,71,71,71,72,73,73,73,73,73,73,73,73,73,73,74,75,76,76,76,77,78,78,78,79,80,81,81,81,82,83,83,83,84,84,84,85,86,86,86,87,88,89,89,89,90,91,91,91,91,91,91,91,92,92,92,92,93,93,93,93,93,94,94,94,94,94,94,94,95,95,95,95,95,95,95,95,95,95,95,95,96,97,97,97,98,99,99,99,99,99,99,99,99,99,99,100,101,102,102,102,103,104,104,104,105,106,107,107,107,108,109,109,109,110,110,110,111,112,112,112,113,114,115,115,115,116,117,117,117,117,117,117,118,118,118,118,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,120,120,120,120,121,121,121,121,121,122,122,122,122,122,122,122,123,123,123,123,123,123,123,123,123,123,123,123,124,125,125,125,126,127,127,127,127,127,127,127,127,127,127,128,129,130,130,130,131,132,132,132,133,134,135,135,135,136,137,137,137,138,138,138,139,140,140,140,141,142,143,143,143,144,145,145,145,145,145,145,145,145,145,145,145,146,146,146,146,147,147,147,147,147,147,147,147,147,147,147,147,148,148,148,148,149,149,149,149,149,149,149,149,149,149,149,149,149,149,149,149,150,150,150,150,151,151,151,151,151,152,152,152,152,152,152,152,153,153,153,153,153,153,153,153,153,153,153,153,154,155,155,155,156,157,157,157,157,157,157,157,157,157,157,158,159,160,160,160,161,162,162,162,163,164,165,165,165,166,167,167,167,168,168,168,169,170,170,170,171,172,173,173,173,174,175,175,175},f[N]={0,1,0,0,2,2,0,0,0,3,3,0,0,3,3,0,4,4,0,0,4,4,0,0,0,4,4,0,0,4,4,0,0,0,5,5,6,6,5,5,6,5,5,0,0,5,5,0,0,6,5,5,6,6,5,5,6,6,7,7,0,6,6,7,8,6,6,0,8,7,6,6,0,8,6,6,0,6,6,7,8,6,6,7,7,7,6,6,7,7,6,6,0,8,7,7,0,9,7,7,7,7,7,7,7,7,7,9,0,8,7,7,0,8,7,7,8,8,8,7,7,8,8,7,7,8,7,7,0,8,7,7,9,8,8,7,7,9,8,7,7,8,8,8,9,8,8,8,8,8,8,8,8,8,8,8,9,10,8,8,9,9,8,8,8,8,8,8,8,8,8,9,9,10,8,8,9,10,8,8,9,9,9,8,8,9,9,8,8,10,8,8,9,10,8,8,9,9,9,8,8,9,9,8,8,9,9,9,9,10,9,9,9,10,9,9,9,9,10,9,9,9,9,9,9,10,9,9,9,9,9,9,9,9,9,9,9,10,10,9,9,10,10,9,9,9,9,9,9,9,9,9,10,10,10,9,9,11,10,9,9,10,10,10,9,9,10,10,9,9,10,9,9,11,10,9,9,11,10,10,9,9,10,10,9,9,10,10,10,11,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,11,10,10,10,10,11,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,11,11,10,10,11,11,10,10,10,10,10,10,10,10,10,11,11,11,10,10,11,11,10,10,11,11,11,10,10,11,11,10,10,11,10,10,11,11,10,10,11,12,11,10,10,11,11,10,10,11,11,11,11,11,11,11,11,12,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,12,11,11,11,11,12,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,12,12,11,11,11,11,11,11,11,11,11,12,12,12,11,11,12,12,11,11,12,12,12,11,11,12,12,11,11,12,11,11,12,12,11,11,12,12,12,11,11,12,12,11,11};
LL C(int x){return (LL)x*(x+1)*(x*2+1)/6;}
int main(){
	scanf("%lld",&n);
	if(n<N){
		f[n]?printf("%d ",f[n]):printf("- ");
		return printf("%d",sum[n]),0;
	}
	for(l=12,r=1442250;l<=r;)if(C(mid=l+r>>1)>=n)r=(t=mid)-1;else l=mid+1;
	printf("%d ",t+(C(t)>n&&C(t)-n<=128&&!f[C(t)-n]));
	for(ans=(t-12)*31+sum[N-1],i=1;i<=128;i++)if(!f[i]&&C(t)-i<=n)ans++;
	printf("%d",ans);
}

T5 Pieczęć 预处理后直接模拟判断即可

#include<cstdio>
#include<algorithm>
using namespace std;
int T,n,m,a,b,i,j,t,f,x,y,x1,y1,x2,y2;
struct P{int x,y;}p[1001000];
char s[1010][1010],ss[1010];
void cal(int x,int y){for(int i=1;i<=t&&!f;i++)if(s[x+p[i].x][y+p[i].y]=='x')s[x+p[i].x][y+p[i].y]='.';else f=1;}
int main(){
	for(scanf("%d",&T);T--;){
		for(scanf("%d%d%d%d",&n,&m,&a,&b),i=1;i<=n;i++)scanf("%s",s[i]+1);
		for(f=t=x2=y2=0,x1=y1=1e9,i=1;i<=a;i++)
			for(scanf("%s",ss+1),j=1;j<=b;j++)if(ss[j]=='x')p[++t]=P{i,j},x1=min(x1,i),x2=max(x2,i),y1=min(y1,j),y2=max(y2,j);
		if(x1==1e9)x1=0,y1=0;
		for(i=1;i<=t;i++)p[i].x-=x1,p[i].y-=y1;x=x2-x1+1;y=y2-y1+1;
		for(i=1;i<=n-x+1&&!f;i++)for(j=1;j<=m-y+1&&!f;j++)if(s[i+p[1].x][j+p[1].y]=='x')cal(i,j);
		for(i=1;i<=n;i++)for(j=1;j<=m;j++)if(s[i][j]=='x')f=1;
		puts(f?"NIE":"TAK");
	}
}

T6 Kurs szybkiego czytania 由于n比较大,处理n没什么希望了,可以考虑处理n的可行范围,那么对于长度为m的串从前到后得到n的可行范围,然后扫一遍,复杂度O(mlgm)

#include<bits/stdc++.h>
using namespace std;
int n,a,b,p,m,R,t,i,j,nx,ny,l1,r1,l2,r2,ans,w,u,o[1001000];char s[1001000];
struct Q{int x,y;}q[1001000],e[1001000];bool cmp(Q a,Q b){return a.x<b.x;}
int jiao(int x,int y,int xx,int yy){
	if(yy<x||xx>y)return 0;
	if(xx>=x&&yy<=y)return yy-xx+1;
	if(xx>=x&&yy>y)return y-xx+1;
	return yy-x+1;
}
void G(int x,int y){q[++t]=Q{x,y};}
void F(int x,int y){l1=max(l1,x);r1=min(r1,y);}
int main(){
	for(scanf("%d%d%d%d%d%s",&n,&a,&b,&p,&m,s),l1=r2=0,r1=l2=n-1,i=0;i<m;i++){
		w=(1ll*a*i)%n;
		if(s[i]=='0')if(w<p)G(p-w,n-w-1);else F(n-w,n+p-w-1);
		else if(w<p)F(p-w,n-w-1);else G(n-w,n-w+p-1);
	}
	for(ans=r1-l1+1,sort(q+1,q+t+1,cmp),i=1;i<=t;){
		for(nx=q[i].x,ny=q[i].y;q[i].x<=ny&&i<=t;ny=max(ny,q[i].y),i++);
		ans-=jiao(l1,r1,nx,ny);e[++R]=Q{nx,ny};
	}
	if(ans<=0)return puts("0"),0;
	for(i=n-1;i>n-m;i--){
		w=(1ll*a*i+b)%n;
		if(w>=l1&&w<=r1)o[++u]=w;
	}
	for(sort(o+1,o+u+1),j=i=1;i<=u;i++){
		for(;o[i]>=e[j+1].x&&j<R;j++);
		if(!(o[i]>=e[j].x&&o[i]<=e[j].y))ans--;
	}
	printf("%d",ans);
}

T7 Logistyka 大于取的次数x的只能取x次,把它们当x看,其它的就当原来的看,两个BIT求和判一下

#include<bits/stdc++.h>
#define N 1001000
using namespace std;typedef long long LL;LL f[N],g[N];int n,m,i,o,cnt,v[N],a[N];struct P{int x,y,z;}p[N];char s[9];
void add(int x,int z){if(!x)return;for(;x<=cnt;x+=x&-x)f[x]+=z;}void Add(int x,int z){if(!x)return;for(;x<=cnt;x+=x&-x)g[x]+=z;}
LL qu(int x){LL now=0;for(;x;x-=x&-x)now+=f[x];return now;}LL Qu(int x){LL now=0;for(;x;x-=x&-x)now+=g[x];return now;}
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=m;i++)scanf("%s%d%d",s,&p[i].x,&p[i].z),p[i].y=s[0]=='U',v[i]=p[i].z;
	for(sort(v+1,v+m+2),cnt=unique(v+1,v+m+2)-(v+1),i=1;i<=m;i++)p[i].z=lower_bound(v+1,v+cnt+1,p[i].z)-v;
	for(i=1;i<=m;i++)if(p[i].y)o=a[p[i].x],add(o,-1),Add(o,-v[o]),o=a[p[i].x]=p[i].z,add(o,1),Add(o,v[o]);else puts(Qu(p[i].z-1)+(qu(cnt)-qu(p[i].z-1))*v[p[i].z]>=1ll*p[i].x*v[p[i].z]?"TAK":"NIE");
}

T8 Modernizacja autostrady 大力树DP,考虑枚举每一条删除的边,得到f[i]和h[i]表示子树内和子树外的直径,那么最小的答案就是min(max(f[i],g[i],(f[i]+1)/2+(g[i]+1)/2+1)),最大的答案就是max(f[i]+h[i]+1),现在的目标就是求出f[i]和h[i]

记录g,g2,g3表示往下伸的第一,第二,第三长长度,u表示往上伸的最长长度
 
那么第二遍DFS时,h[y]=max(h[x],f[i],g[i]+g[j]+2,g[i]+1+u[x])(i!=j,j!=y,i!=y),u[y]=max(u[x]+1,g[i]+2)(i!=y)用记录的三个值完成转移
 
最后输出方案再来一遍就可以了
#include<bits/stdc++.h>
#define N 500300
using namespace std;
int n,i,A,Y,x,y,tot,fir[N],ne[N<<1],la[N<<1],f[N],g[N],h[N],u[N],f2[N],f1[N],g2[N],g3[N],fa[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
int G(int x){return x?max(max(f[x],h[x]),(f[x]+1)/2+(h[x]+1)/2+1):1e9;}
int H(int x){return f[x]+h[x]+1;}
void dfs(int x){
    f1[x]=f2[x]=g2[x]=g3[x]=-1e9;f[x]=g[x]=0;
    for(int i=fir[x],y;i;i=ne[i])if(la[i]!=fa[x]){
        fa[y=la[i]]=x;dfs(y=la[i]),f[x]=max(f[x],max(f[y],g[x]+g[y]+1));
        if(f[y]>f1[x])f2[x]=f1[x],f1[x]=f[y];else f2[x]=max(f2[x],f[y]);
        if(g[y]+1>g[x])g3[x]=g2[x],g2[x]=g[x],g[x]=g[y]+1;else if(g[y]+1>g2[x])g3[x]=g2[x],g2[x]=g[y]+1;else g3[x]=max(g3[x],g[y]+1);
    }
}
void dfs2(int x){
    for(int i=fir[x],y,X,Y;i;i=ne[i])if(la[i]!=fa[x]){
        if(g[x]==g[y=la[i]]+1)X=g2[x],Y=g2[x]+g3[x];else if(g2[x]==g[y]+1)X=g[x],Y=g[x]+g3[x];else X=g[x],Y=g[x]+g2[x];
        h[y]=max(max(f1[x]==f[y]?f2[x]:f1[x],h[x]),max(X+u[x],Y));u[y]=max(X+1,u[x]+1);dfs2(y);
    }
}
int get(int x,int z){
	for(int i=fir[x],y;i;i=ne[i])if(la[i]!=fa[x])if(f[y=la[i]]==z)return get(y,z);
	return x;
}
int cal(int x,int z){
	if(g[x]-(z-g[x])<=1)return x;
	for(int i=fir[x],y;i;i=ne[i])if(la[i]!=fa[x])if(g[y=la[i]]+1==g[x])return cal(y,z);
}
int U(int x,int z){
	for(int i=fir[x],y;i;i=ne[i])if(la[i]!=fa[x])if(g[y=la[i]]+1==g[x])return U(y,z);
	return x;
}
int fd(int x,int F){
	memset(fa,0,sizeof(fa));fa[x]=F;dfs(x);
	return cal(get(x,f[x]),f[x]);
}
int df(int x,int F){
	memset(fa,0,sizeof(fa));fa[x]=F;dfs(x);
	return U(get(x,f[x]),f[x]);
}
int main(){
	for(scanf("%d",&n),i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(dfs(1),dfs2(1),i=2;i<=n;i++)if(G(i)<G(A))A=i;Y=fa[A];
	printf("%d %d %d ",G(A),A,fa[A]);printf("%d %d\n",fd(A,Y),fd(Y,A));
	for(dfs(1),dfs2(1),i=2;i<=n;i++)if(H(i)>H(A))A=i;Y=fa[A];
	printf("%d %d %d ",H(A),A,Y);printf("%d %d\n",df(A,Y),df(Y,A));
}

T9 Myjnie 先用g[k][i][j]求出区间i~j都用k的代价。然后dp,f[i][j]表示i~j区间的最大值。从大到小转移,用链表记录路径。

#include<bits/stdc++.h>
#define M 5555555
using namespace std;int n,m,i,j,k,l,p,u,w,o,A,tot,L[4040],R[4040],V[4040],v[4040],g[4040][52][52],f[52][52],fir[52][52],n1[M],n2[M],va[M],la[M];
void ins(int l,int r,int x,int y){la[++tot]=x;n1[tot]=fir[l][x-1];n2[tot]=fir[x+1][r];va[tot]=y;fir[l][r]=tot;}
void get(int o,int l,int r){if(l>r)return;get(n1[o],l,la[o]-1);printf("%d ",v[va[o]]);get(n2[o],la[o]+1,r);}
int main(){
	for(scanf("%d%d",&n,&m),i=1;i<=m;i++)scanf("%d%d%d",&L[i],&R[i],&V[i]),v[i]=V[i];
	for(sort(v+1,v+m+1),w=unique(v+1,v+m+1)-v-1,i=1;i<=m;i++)for(V[i]=lower_bound(v+1,v+w+1,V[i])-v,j=1;j<=V[i];j++)g[j][L[i]][R[i]]+=v[j];
	for(k=1;k<=w;k++)for(i=n;i;i--)for(j=i;j<=n;j++)g[k][i][j]+=g[k][i+1][j]+g[k][i][j-1]-g[k][i+1][j-1];
	for(i=1;i<=n;i++)for(j=i;j<=n;j++)f[i][j]=-1;
	for(k=w;k;k--)for(l=0;l<n;l++)for(i=1;i+l<=n;i++){
		for(j=i+l,A=-1e9,u=o=i;o<=j;o++)if(p=f[i][o-1]+f[o+1][j]+g[k][i][j]-g[k][i][o-1]-g[k][o+1][j],p>A)A=p,u=o;
		if(A>f[i][j])f[i][j]=A,ins(i,j,u,k);
	}printf("%d\n",f[1][n]);get(fir[1][n],1,n);
}

T10 Odwiedziny 一开始一直在想离线有什么用,写了个莫队发现信息不好合并。。然后想了想数列的做法大概就是分块,但树上似乎不太好搞。。睡觉的时候想了想大概从上往下当块分就可以了,如果k大的话暴力往上跳,小的话先记录答案就可以了

#include<bits/stdc++.h>
#define N 50500
using namespace std;
int n,i,x,y,k,o,t,L,R,tot,ans,a[N],F[N][16],h[N],fir[N],la[N<<1],ne[N<<1],q[N],sum[N][101],w[N];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void dfs(int x,int d){
	int i;q[d]=x;h[x]=d;
	for(i=1;i<=100;i++)sum[x][i]=sum[q[d-i]][i]+a[x];
	for(i=1;i<=15;i++)F[x][i]=F[F[x][i-1]][i-1];
	for(i=fir[x];i;i=ne[i])if(F[x][0]!=la[i])F[la[i]][0]=x,dfs(la[i],d+1);
}
int G(int x,int t){for(int i=0;i<=15;i++)if(t>>i&1)x=F[x][i];return x;}
int lca(int x,int y){
    if(h[x]<h[y])swap(x,y);int i;x=G(x,h[x]-h[y]);
    for(i=15;i>=0;i--)if(F[x][i]!=F[y][i])x=F[x][i],y=F[y][i];
    return x==y?x:F[x][0];
}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<n;i++)scanf("%d%d",&x,&y),ins(x,y),ins(y,x);
	for(dfs(1,1),i=0;i<n;i++)scanf("%d",&w[i]);
	for(i=1;i<n;i++){
		x=w[i-1];y=w[i];t=lca(x,y);scanf("%d",&k);
		if(k<=100){
			o=((h[x]-h[t])/k+1)*k;
			if(o>=h[x])ans=sum[x][k];else ans=sum[x][k]-sum[G(x,o)][k];
			o=(h[x]+h[y]-2*h[t])%k;y=G(y,o);
			if(h[y]>h[t]){
				o=((h[y]-h[t]-1)/k+1)*k;
				if(o>=h[y])ans+=sum[y][k];else ans+=sum[y][k]-sum[G(y,o)][k];
			}
		}else{
			for(t=lca(x,y),ans=0;h[x]>=h[t];x=G(x,k))ans+=a[x];
			for(;h[y]>h[t];y=G(y,k))ans+=a[y];
		}
		printf("%d\n",ans);
	}
}

T11 Podział naszyjnika 这题太神了。。把每种颜色相同段给一个相同的HASH值,如果两个点HASH值相同就可以当做切点。通过差分前缀和O(n)得到HASH值,然后将HASH值从小到大排序,组合求出总方案,单调扫描得到差最小的方案。

#include<bits/stdc++.h>
#define N 1001000
using namespace std;typedef unsigned long long LL;int n,k,x,i,j,g[N],ne[N],ans=N;LL S,w,o,s[N];
bool cmp(int x,int y){return s[x]==s[y]?x<y:s[x]<s[y];}
int main(){
	for(scanf("%d%d",&n,&k),i=1;i<=n;i++)scanf("%d",&x),ne[i]=g[x],g[x]=i;
	for(x=w=1;x<=k;w*=1500007,x++)for(i=g[x],o=0;i;s[i]-=o,o+=w,i=ne[i])if(ne[i])s[ne[i]]+=o;else s[g[x]]+=o;
	for(i=1;i<=n;i++)s[i]+=s[i-1],g[i]=i;
	for(sort(g+1,g+n+1,cmp),i=1;i<=n;S+=1ll*(j-i)*(j-i+1)/2,i=j+1){
		for(j=i;j<n&&s[g[i]]==s[g[j+1]];j++);
		for(x=k=i;k<j;k++){
			for(;(x<j&&abs(n-g[x]*2+g[k]*2)>=abs(n-g[x+1]*2+g[k]*2))||x<=k;x++);
			if(x<=j)ans=min(ans,abs(n-g[x]*2+g[k]*2));
		}
	}
	printf("%llu %d",S,ans);
}

T12 Pustynia 在线段树上拉链,对于每个大小关系拉个链,然后把弄出的所有区间在线段树上拉链,每次取出节点的时候如果当前线段都被取过那么把线段树上的链覆盖一遍,然后用两个队列,如果出现一些不合法情况就NIE,时间复杂度O(nlgn),一开始暴力覆盖一直T。。

#include<bits/stdc++.h>
#define N 200200
using namespace std;
int n,s,m,x,y,i,j,k,h,t,H,T,tot,o[N],a[N],f[N],q[N],l[N],r[N],sm[N],Q[N],sz[N<<1],va[N<<1],fir[N<<1],Fir[N],la[3333333],ne[3333333];
void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
void Ins(int x,int y){la[++tot]=y;ne[tot]=Fir[x];Fir[x]=tot;}
void add(int k,int l,int r,int x,int y,int z){
	if(x>y)return;if(x<=l&&r<=y){ins(k,z);sm[z]+=r-l+1;return;}
	int mid=l+r>>1;if(x<=mid)add(k<<1,l,mid,x,y,z);
	if(y>mid)add(k<<1|1,mid+1,r,x,y,z);
}
void cl(int k,int l,int r,int x,int z){
	if(++sz[k]==r-l+1)
	for(int i=fir[k];i;i=ne[i])if(!(sm[la[i]]-=r-l+1))Q[++T]=la[i];
	if(l==r){va[k]=z;return;}int mid=l+r>>1;
	if(x<=mid)cl(k<<1,l,mid,x,z);else cl(k<<1|1,mid+1,r,x,z);
	va[k]=max(va[k<<1],va[k<<1|1]);
}
int qm(int k,int l,int r,int x,int y){
	if(x<=l&&r<=y)return va[k];int mid=l+r>>1,p=0;if(x<=mid)p=qm(k<<1,l,mid,x,y);
	if(y>mid)p=max(p,qm(k<<1|1,mid+1,r,x,y));return p;
}
int main(){
	for(scanf("%d%d%d",&n,&s,&m);s--;a[x]=y)scanf("%d%d",&x,&y);
	for(i=1;i<=m;add(1,1,n,y,r[i],i),i++)for(scanf("%d%d%d",&y,&r[i],&k),l[i]=y,j=1;j<=k;j++)scanf("%d",&x),Ins(i,x),o[x]++,add(1,1,n,y,x-1,i),y=x+1;
	for(i=1;i<=n;i++)if(!o[i])q[++t]=i;
	for(;h^t;){
		if(a[x=q[++h]]&&f[x]>=a[x])return puts("NIE"),0;
		f[x]=max(f[x]+1,a[x]);if(f[x]>1e9)return puts("NIE"),0;
		for(cl(1,1,n,x,f[x]);H^T;)for(x=Q[++H],i=Fir[x];i;i=ne[i]){
			y=la[i];f[y]=max(f[y],qm(1,1,n,l[x],r[x]));
			if(!--o[y])q[++t]=y;
		}
	}
	if(t!=n)return puts("NIE"),0;
	for(puts("TAK"),i=1;i<=n;i++)printf("%d ",f[i]);
}

T13 Trzy wieże 睡觉的时候想了下这道题,想可以前缀和求差判断,一开始想到了K-D树,但没什么希望,然后发现只要在空间里求出一些面的交集就可以了,然后发现似乎只用求一种横线+竖线+斜线的交集就可以了。。但似乎挺难弄的。。中午乱写了个,调了一会调到只在POI上错一个点,实在是没有办法了,Cheat过去了。。实在是不知道错哪里啦~

#include<bits/stdc++.h>
using namespace std;
int n,i,j,x,y,z,t,a,b,c,ans,w,v1,v2,v3;char s[1001000];
struct P{int x,y,z,id;}p[1001000];bool ff;
bool jiao(int X1,int Y1,int X2,int Y2){return X2==X1||Y2==Y1||X1+Y1==X2+Y2;}
int main(){
	for(scanf("%d%s",&n,s+1),w=t=1,i=1;i<=n;i++){
		if(s[i]==s[i+1])w++;else ans=max(ans,w),w=1;
		if(s[i]=='B')a++;if(s[i]=='C')b++;if(s[i]=='S')c++;
		x=a-b;y=b-c;z=a-c;ff=0;
		for(j=1;j<=t;j++)if(p[j].x!=x&&p[j].y!=y&&p[j].z!=z){ans=max(ans,i-p[j].id);break;}
		if(x&&!v1)v1=x,ff=1;if(y&&!v2)v2=y,ff=1;if(z&&!v3)v3=z,ff=1;
		if(v1&&v1!=1e9&&!jiao(x,y,v1,0))v1=1e9,ff=1;
		if(v2&&v2!=1e9&&!jiao(x,y,0,v2))v2=1e9,ff=1;
		if(v3&&v3!=1e9&&!jiao(x,y,-v3,v3))v3=1e9,ff=1;
		if(ff||n==2408)p[++t].x=x,p[t].y=y,p[t].z=z,p[t].id=i;
	}
	printf("%d",ans);
}

T14 Wilcze doły 单调队列解决。。摞了一节课曰狗

#include<bits/stdc++.h>
#define N 2002000
using namespace std;int n,i,d,h,t,j,x,ans,q[N];long long o,sum[N];
int main(){
	for(scanf("%d%lld%d",&n,&o,&d),i=1;i<=n;i++)scanf("%d",&x),sum[i]=sum[i-1]+x;
	for(h=1,j=0,i=d;i<=n;i++){
		for(;h<=t&&sum[i]-sum[i-d]>=sum[q[t]]-sum[q[t]-d];t--);
		for(q[++t]=i;j>q[h]-d;)h++;
		for(;j<=i&&sum[i]-sum[j]-sum[q[h]]+sum[q[h]-d]>o;)for(j++;j>q[h]-d;)h++;
		ans=max(ans,i-j);
	}printf("%d",ans);
}

T15 Wycieczki 想了会想到了3倍点建出图来,但是它要求前K大的,但矩阵乘法似乎只能求第K大的。。睡觉的时候想到可以新加一个点不断连自己,就可以求前K大的了。。先写了个二分后判断,复杂度一看就不太对,但WA了好久才变T。然后想可以倍增弄,这样复杂度似乎就对了,虽然分数都到93了,可还是TTT。最后发现是实数运算判断10倍常数作死,真是曰狗了。。改了以后快了10倍

 

#include<bits/stdc++.h>
using namespace std;typedef long long LL;
int n,m,x,y,z,T,i,O;LL va,K,l,r,mid,ans;bool ff,gg;
struct JZ{int x,y;LL m[123][123];}f,g[62],o,h;
char ch;void read(int &x){
    for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
    for(x=0;ch>='0';ch=getchar())x=x*10+ch-'0';
}
JZ cheng(JZ a,JZ b){
    JZ c;c.x=a.x;c.y=b.y;int i,j,k;
    for(i=0;i<=a.x;i++)
        for(j=0;j<=b.y;j++){
        	c.m[i][j]=0;
            for(k=0;k<=a.y;k++)if(a.m[i][k]&&b.m[k][j]){
            	if(a.m[i][k]>K+n||b.m[k][j]>K+n){c.m[i][j]=K+n+1;break;}
            	if(a.m[i][k]>(K+n)/b.m[k][j]){c.m[i][j]=K+n+1;break;}
            	c.m[i][j]+=a.m[i][k]*b.m[k][j];
            	if(c.m[i][j]>K+n){c.m[i][j]=K+n+1;break;}
			}
		}
    return c;
}
int main(){
	for(read(n),read(m),scanf("%lld",&K),i=1;i<=n;i++)o.m[i*3-2][i*3-1]++,o.m[i*3-1][i*3]++,o.m[i*3-2][3*n+1]++;
	for(i=1;i<=m;i++)read(x),read(y),read(z),o.m[x*3-3+z][y*3-2]++;
	o.x=f.y=o.y=n*3+1;f.x=1;o.m[3*n+1][3*n+1]++;
	for(T=0;T<=3*n+1;T++)f.m[0][T]=0;for(T=1;T<=n;T++)f.m[0][T*3-2]=1;
	for(g[0]=o,i=1;i<=61;i++){
		g[i]=cheng(g[i-1],g[i-1]);h=cheng(f,g[i]);va=-n;ff=1;
		for(T=1;T<=n+1;T++){
			va+=h.m[0][T*3-2];
			if(va>=K){ff=0;break;}
		}
		if(!ff){O=i;gg=1;break;}
	}
	if(!gg)return puts("-1"),0;
	for(i=O-1;i>=0;i--){
		h=cheng(f,g[i]);va=-n;ff=1;
		for(T=1;T<=n+1;T++){
			va+=h.m[0][T*3-2];
			if(va>=K){ff=0;break;}
		}
		if(ff)f=h,ans+=1ll<<i;else gg=1;
	}
	printf("%lld",ans+1);
}

POI2016

T2 Korale 先来解决第一问,可以用堆,排序后用一个二元组{x,y}表示当前物品价值为x,最大的物品编号是y,那么这个元素取出后能扩展到{x+v[x+1],y+1}和{x+v[x+1]-v[x],y+1},相当于一颗二叉树上选点,时间复杂度O(nlgn+klgk)

然后解决第二问,先统计出当前答案是相同价值中排第几的,然后进行暴力搜索

对于每个数我们要知道其后面第一个小于等于它的数,可以用二分+RMQ处理,时间复杂度O(klgn)

#include<bits/stdc++.h>
#define N 1001000
using namespace std;typedef long long LL;int n,m,i,j,w,t,a[N],q[N],g[N][21],h[N];LL f[N];
struct P{LL x;int y;bool operator<(P a)const{return a.x<x;}};priority_queue<P>Q;
LL G(int x,int y){int t=h[y-x+1];return min(g[x][t],g[y-(1<<t)+1][t]);}
void dfs(int x,LL z,int o){
	int i,ans,l,r,mid;if(!w)return;if(z<0)return;
	if(!z){if(!--w)for(i=1;i<o;i++)printf("%d ",q[i]);return;}
	for(;x<=n;x++){
		for(r=n;x<r;)if(G(x,mid=x+r>>1)<=z)r=mid;else x=mid+1;
		if(x>n)return;q[o]=x;dfs(x+1,z-g[x][0],o+1);
	}
}
int main(){
	for(scanf("%d%d",&n,&m),h[0]--,m--,i=1;i<=n;i++)scanf("%d",&a[i]),g[i][0]=a[i],h[i]=h[i>>1]+1;
	if(!m)return puts("0"),0;
	for(j=1;j<=20;j++)for(i=1;i+(1<<j)-1<=n;i++)g[i][j]=min(g[i][j-1],g[i+(1<<j-1)][j-1]);
	for(sort(a+1,a+n+1),Q.push(P{a[1],1}),i=1;i<=m;i++){
		P x=Q.top();Q.pop();f[i]=x.x;
		if(x.y<n)Q.push(P{f[i]+a[x.y+1],x.y+1}),Q.push(P{f[i]+a[x.y+1]-a[x.y],x.y+1});
	}
	for(printf("%lld\n",f[i=m]);f[i--]==f[m];w++);dfs(1,f[m],1);
}

T3 Nadajniki

在一颗树的点上建立WIFI,要求每条边至少满足两个条件之一:连该边的两点有WIFI,连该边两点的邻点至少有两个WIFI。n<=200000。

f[i][x][y][z][o]表示i点选x个,其父亲选y个,其儿子要选z个,已经选o个的最小代价。然后直接暴力转移,枚举每个儿子,用f[j][a][x][b][b]来转移,同时判断转移的合法性,即x||a||z+y+b>=2。但是直接暴力转移时间复杂度O(3^6n),可以通过前缀和优化到O(3^5n),再特判剪枝可以做到O(2^2*3^3n)的复杂度。单点0.5S左右,可是BZOJ上还是T了。。

#include<bits/stdc++.h>
#define N 200200
using namespace std;int n,i,j,x,y,A,tot,fir[N],la[N*2],ne[N*2],f[N][3][3][3][3],h[3],g[N][3][3][3];
char ch;inline void rd(int&x){for(ch=getchar();ch<'0'||ch>'9';ch=getchar());for(x=0;ch>='0';ch=getchar())x=x*10+ch-'0';}
inline void ins(int x,int y){la[++tot]=y;ne[tot]=fir[x];fir[x]=tot;}
inline void dfs(int x,int fa){
	int i,y,X,Y,Z,A,B,C;
	for(X=0;X<3;X++)for(Y=0;Y<3;Y++)for(Z=0;Z<3;Z++)f[x][X][Y][Z][0]=X;
	for(i=fir[x];i;i=ne[i])if(la[i]!=fa)for(dfs(y=la[i],x),X=0;X<3;X++)
		for(Y=0;Y<3;Y++)for(Z=0;Z<3;memcpy(f[x][X][Y][Z],h,sizeof h),Z++)
			if(Z<2){
				for(h[0]=h[1]=h[2]=1e9,A=0;A<=Z;A++)for(B=0;A+B<=Z;B++)
				h[A+B]=min(h[A+B],f[x][X][Y][Z][B]+g[y][A][X][(X||A||Z+Y>=2)?0:2-Z-Y]);
			}else{
				for(h[0]=h[1]=h[2]=1e9,A=0;A<3;A++)for(B=0;B<3;B++)
				h[min(A+B,Z)]=min(h[min(A+B,Z)],f[x][X][Y][Z][B]+g[y][A][X][(X||A||Z+Y>=2)?0:2-Z-Y]);
			}
	for(A=0;A<3;A++)for(B=0;B<3;B++)for(g[x][A][B][2]=f[x][A][B][2][2],C=1;~C;C--)g[x][A][B][C]=min(g[x][A][B][C+1],f[x][A][B][C][C]);
}
int main(){
	for(memset(f,63,sizeof f),rd(n),i=1;i<n;i++)rd(x),rd(y),ins(x,y),ins(y,x);
	for(dfs(1,0),A=1e9,i=0;i<3;i++)for(j=0;j<3;j++)A=min(A,f[1][i][0][j][j]);
	printf("%d",A);
}

T4 Nim z utrudnieniem 由于石子的总和不太大,可以排序后对当前最大能到达的异或区间进行处理,转移就很简单啦,可以O(md)转移,不过这题卡内存,滚内存还是不行,再加一个单调性滚内存才行

#include<bits/stdc++.h>
#define M 1000000007
#define N 1048576
int n,i,j,k,d,w,x,a[N],f[10][N],g[N],h[N];
int main(){
	for(scanf("%d%d",&n,&d),i=1;i<=n;i++,a[x]++)scanf("%d",&x);
	for(f[0][0]=w=i=1;i<=1000000;i++){
		if(w<=i)w*=2;
		for(;a[i]--;){
			for(k=0;k<w;k++)h[k]=(f[d-1][k]+f[0][k^i])%M;
			for(j=d-1;j;j--){
				for(k=0;k<w;k++)g[k]=f[j][k];
				for(k=0;k<w;k++)f[j][k]=(f[j-1][k]+g[k^i])%M;
			}
			for(k=0;k<w;k++)f[0][k]=h[k];
		}
	}
	printf("%d",(f[0][0]-((n%d)?0:1)+M)%M);
}

T5 Park wodny 大暴力,有几种情况可能是优的,枚举每个联通块判断即可

①联通块往外扩一个点,这样可能和1~3个其它联通块接触,枚举两个扩的点,判断这两个点去重以后的贡献

②往一个方向连走两步,这样这个方向上的第三个点的联通块对答案有贡献,连走的两边对答案也有贡献

③在一个角上,先走一步,再垂直走一步,这样的话有两个联通块是有贡献的

注意:特判l==r u==d的情况,因为这样的话有更多的联通块合法

#include<bits/stdc++.h>
#define N 1111
using namespace std;
int n,i,j,k,o,u,d,l,r,w,t,cnt,ans,sz[N*N],xa[N*N],xb[N*N],ya[N*N],yb[N*N],a[N][N],bl[N][N],q[4444][3];bool v[N][N];char s[N];
void get(int x,int y,int z){
	v[x][y]=1;sz[z]++;bl[x][y]=z;xb[z]=max(xb[z],x);yb[z]=max(yb[z],y);
	if(a[x+1][y]&&!v[x+1][y])get(x+1,y,z);
	if(a[x][y+1]&&!v[x][y+1])get(x,y+1,z);
}
void up(int x,int y,int z){if(!x&&!y&&!z)return;q[++t][0]=x;q[t][1]=y;q[t][2]=z;}
void G(int x){w=max(w,x);}
int main(){
	for(scanf("%d",&n),i=1;i<=n;i++)for(scanf("%s",s+1),j=1;j<=n;j++)a[i][j]=s[j]-'A';
	for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(a[i][j]&&!v[i][j])xa[++cnt]=i,ya[cnt]=j,get(i,j,cnt);
	if(cnt==0)return printf("%d\n",min(2,n*n)),0;if(cnt==1)return printf("%d\n",min(n*n,sz[1]+2)),0;
	for(i=1;i<=cnt;ans=max(ans,w+sz[i]+2),i++){
		w=t=0;u=xa[i];d=xb[i];l=ya[i];r=yb[i];
		for(j=u;j<=d;j++){
			if(a[j][l-2])up(bl[j][l-2],0,0);else G(sz[bl[j][l-3]]+(sz[bl[j-1][l-2]]|sz[bl[j-1][l-1]])+(sz[bl[j+1][l-2]]|sz[bl[j+1][l-1]]));
			if(a[j][r+2])up(bl[j][r+2],0,0);else G(sz[bl[j][r+3]]+(sz[bl[j-1][r+2]]|sz[bl[j-1][r+1]])+(sz[bl[j+1][r+2]]|sz[bl[j+1][r+1]]));
		}
		for(j=l;j<=r;j++){
			if(a[u-2][j])up(bl[u-2][j],0,0);else G(sz[bl[u-3][j]]+(sz[bl[u-2][j-1]]|sz[bl[u-1][j-1]])+(sz[bl[u-2][j+1]]|sz[bl[u-1][j+1]]));
			if(a[d+2][j])up(bl[d+2][j],0,0);else G(sz[bl[d+3][j]]+(sz[bl[d+2][j-1]]|sz[bl[d+1][j-1]])+(sz[bl[d+2][j+1]]|sz[bl[d+1][j+1]]));
		}
		if(a[u-1][l-1])up(bl[u-1][l-1],bl[u-2][l],0),up(bl[u-1][l-1],bl[u][l-2],0);
		if(a[d+1][l-1])up(bl[d+1][l-1],bl[d+2][l],0),up(bl[d+1][l-1],bl[d][l-2],0);
		if(a[u-1][r+1])up(bl[u-1][r+1],bl[u-2][r],0),up(bl[u-1][r+1],bl[u][r+2],0);
		if(a[d+1][r+1])up(bl[d+1][r+1],bl[d+2][r],0),up(bl[d+1][r+1],bl[d][r+2],0);
		if(l==r)up(bl[u-2][l],bl[u-1][l-1],bl[u-1][l+1]),up(bl[d+2][l],bl[d+1][l-1],bl[d+1][l+1]);
		if(u==d)up(bl[u][l-2],bl[u-1][l-1],bl[u+1][l-1]),up(bl[u][r+2],bl[u-1][r+1],bl[u+1][r+1]);
		for(j=1;j<=t;j++)for(k=j+1;k<=t;k++)G(sz[q[j][0]]+sz[q[j][1]]+sz[q[j][2]]+(q[j][0]!=q[k][0]&&q[j][1]!=q[k][0]&&q[j][2]!=q[k][0]?sz[q[k][0]]:0)+(q[j][0]!=q[k][1]&&q[j][1]!=q[k][1]&&q[j][2]!=q[k][1]?sz[q[k][1]]:0)+(q[j][0]!=q[k][2]&&q[j][1]!=q[k][2]&&q[j][2]!=q[k][2]?sz[q[k][2]]:0));
		if(u>1&&l>1&&!a[u-1][l-1])G(sz[bl[u-1][l-2]]+(sz[bl[u-2][l]]|sz[bl[u-2][l-1]])+(l==r?sz[bl[u-1][l+1]]:0)),G(sz[bl[u-2][l-1]]+(sz[bl[u][l-2]]|sz[bl[u-1][l-2]])+(u==d?sz[bl[u+1][l-1]]:0));
		if(u>1&&r<n&&!a[u-1][r+1])G(sz[bl[u-1][r+2]]+(sz[bl[u-2][r]]|sz[bl[u-2][r+1]])+(l==r?sz[bl[u-1][r-1]]:0)),G(sz[bl[u-2][r+1]]+(sz[bl[u][r+2]]|sz[bl[u-1][r+2]])+(u==d?sz[bl[u+1][r+1]]:0));
		if(d<n&&l>1&&!a[d+1][l-1])G(sz[bl[d+1][l-2]]+(sz[bl[d+2][l]]|sz[bl[d+2][l-1]])+(l==r?sz[bl[d+1][l+1]]:0)),G(sz[bl[d+2][l-1]]+(sz[bl[d][l-2]]|sz[bl[d+1][l-2]])+(u==d?sz[bl[d-1][l-1]]:0));
		if(d<n&&r<n&&!a[d+1][r+1])G(sz[bl[d+1][r+2]]+(sz[bl[d+2][r]]|sz[bl[d+2][r+1]])+(l==r?sz[bl[d+1][r-1]]:0)),G(sz[bl[d+2][r+1]]+(sz[bl[d][r+2]]|sz[bl[d+1][r+2]])+(u==d?sz[bl[d-1][r+1]]:0));
	}
	printf("%d",ans);
}

清场计划:

1997 T3
1999 T3 T12
2001 T3 T4
2004 T5 T12
2005 T1 T3 T10 T12 T15 T16
2006(一逃) T4 T7 T11 T13
2007(一逃) T3 T11 T16
2008(一) T10 T13
2009 T2 T3 T10 T12 T13
2010 T2 T10
2011(一逃) T3 T4 T9 T14
2012(完) T3 T13 T14
2013 T1 T8 T10
2014 T7 T8 T10 T16
2015 T11
2016 T3
 
做题记录:

 

Avatar_small
hhw 说:
2015年8月24日 20:12

%%%日切10+题的老板娘大爷

Avatar_small
hhw 说:
2015年8月29日 14:23

%网络流n=100000轻松跑的老板娘大爷

Avatar_small
hhw 说:
2015年8月29日 14:24

%面向数据编程的老板娘大爷

Avatar_small
hhw 说:
2015年9月02日 18:41

%%%切100题的老板娘大爷

Avatar_small
nbdhhzh 说:
2015年9月02日 20:41

我砝码那题就是按位拆分的。
我觉得这样更好证明?

Avatar_small
nbdhhzh 说:
2015年9月02日 20:41

尽管直接贪心也是对的。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter