唉……第二天依然被虐……但是比第一天好一点……我必须负责任的指出:志灿大神出的题比柯黑的不知道靠谱到哪里去了……柯黑出的简直不可做
但是被虐的命运是无法改变的……求各位神犇别D我
黄巨大真是强啊,不愧是福建省现役最强!连A两题之后第三题写了个暴力随机点+最小生成树的多跑了几次竟然各种乱A……还把std给hack了……rank1的巨大神!@陈*瑶
【题目描述】
二维平面上有n个点(xi, yi),现在这些点中取若干点构成一个集合S,对它们按照x坐标排序,顺次连接,将会构成一些连续上升、下降的折线,设其数量为f(S)。如下图中,1->2,2->3,3->5,5->6(数字为下图中从左到右的点编号),将折线分为了4部分,每部分连续上升、下降。
现给定k,求满足f(S) = k的S集合个数。
【输入格式】
第一行两个整数n和k,以下n行每行两个数(xi, yi)表示第i个点的坐标。所有点的坐标值都在[1, 100000]内,且不存在两个点,x坐标值相等或y坐标值相等。
【输出格式】
输出满足要求的方案总数 mod100007的结果。
【样例输入】
5 1
5 5
3 2
4 4
2 3
1 1
【样例输出】
19
【数据范围】
对于10%的数据,n <= 10, k = 1
对于30%的数据,n <= 1000, k = 1
对于50%的数据,n <= 1000, k <= 10
对于100%的数据,n <= 50000,0 < k <= 10
第一题可做……就是带优化的dp。但是我逗比了……考场上没想出方程……幸好我及时orz了hzwer巨大神
n^2*m暴力还是可写的
首先按x坐标sort一下,把y坐标提出来。这基本上没问题
用f[i][j][0/1]表示选了第i个点、一共有j段折线、最后一段是上升(1)或者下降(0)的方案数
这里不能选前i个点,因为这样不能知道第i个点是选了还是没选。
所以f[i][j][1]=Σf[k][j][1]+f[k][j-1][0],其中y[k]>y[i]
f[i][j][0]=Σf[k][j][0]+f[k][j-1][1],其中y[k]<y[i];
具体实现什么的看代码吧
#include<cstdio> #include<iostream> #include<algorithm> #define mod 100007 using namespace std; struct point{ int x,y; }p[100001]; inline bool cmp(const point &a,const point &b) { return a.x<b.x; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,m,sum; int a[100001]; int f[1001][1001][2]; int main() { freopen("line.in","r",stdin); freopen("line.out","w",stdout); n=read();m=read(); for (int i=1;i<=n;i++)p[i].x=read(),p[i].y=read(); sort(p+1,p+n+1,cmp); for (int i=1;i<=n;i++)a[i]=p[i].y; for (int i=1;i<=n;i++) { f[i][0][0]=f[i][0][1]=1; for (int j=1;j<=m;j++) { for (int k=1;k<i;k++) if (a[k]<a[i]) { f[i][j][1]+=f[k][j][1]; if (j)f[i][j][1]+=f[k][j-1][0]; while(f[i][j][1]>mod) f[i][j][1]-=mod; } else { f[i][j][0]+=f[k][j][0]; if (j)f[i][j][0]+=f[k][j-1][1]; while(f[i][j][0]>mod) f[i][j][0]-=mod; } } } for (int i=1;i<=n;i++) sum=(sum+f[i][m][0]+f[i][m][1])%mod; printf("%d",sum); }
那么这样dp时间效率是n^2*m的,肯定要T
所以可以考虑维护一个树状数组、以y为关键字,这样可以优化到n*logn*m
刚考完cf又掉rating 没心情再写了。直接上黄巨大代码
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<cstring> #include<algorithm> #define ll long long #define inf 100000000 #define mod 100007 using namespace std; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,m,ans; int t[15][2][100005]; int f[100005][15][2];//0ΪÉÏÉý1ΪϽµ struct data{int x,y;}p[100005]; inline bool operator<(data a,data b) {return a.x<b.x;} inline int lowbit(int x) {return x&(-x);} inline void add(int x,int v,int j,int k) { for(int i=x;i<=100000;i+=lowbit(i)) t[j][k][i]=(t[j][k][i]+v)%mod; } inline int que(int x,int j,int k) { int sum=0; for(int i=x;i;i-=lowbit(i)) sum=(sum+t[j][k][i])%mod; return sum; } void solve1() { for(int i=1;i<=n;i++) { f[i][0][0]=f[i][0][1]=1; for(int j=1;j<i;j++) for(int k=1;k<=m;k++) { if(p[j].y<p[i].y)f[i][k][0]+=f[j][k][0]+f[j][k-1][1]; else f[i][k][1]+=f[j][k][1]+f[j][k-1][0]; f[i][k][0]%=mod;f[i][k][1]%=mod; } } for(int i=1;i<=n;i++) { ans=(ans+f[i][m][0])%mod; ans=(ans+f[i][m][1])%mod; } printf("%d\n",ans); } void solve2() { for(int i=1;i<=n;i++) { f[i][0][0]=f[i][0][1]=1; add(p[i].y,1,0,0);add(p[i].y,1,0,1); for(int j=1;j<=m;j++) { f[i][j][0]+=que(p[i].y-1,j,0)+que(p[i].y-1,j-1,1); f[i][j][1]+=que(100000,j,1)-que(p[i].y,j,1)+que(100000,j-1,0)-que(p[i].y,j-1,0); f[i][j][0]%=mod;f[i][j][1]%=mod; if(f[i][j][1]<0)f[i][j][1]+=mod; add(p[i].y,f[i][j][0],j,0);add(p[i].y,f[i][j][1],j,1); } } for(int i=1;i<=n;i++) { ans=(ans+f[i][m][0])%mod; ans=(ans+f[i][m][1])%mod; } printf("%d\n",ans); } int main() { freopen("line.in","r",stdin); freopen("line.out","w",stdout); n=read(); m=read(); for(int i=1;i<=n;i++) p[i].x=read(),p[i].y=read(); sort(p+1,p+n+1); if(n<=1000)solve1(); else solve2(); return 0; }你看他写得多好啊
还是我太弱了
【题目描述】
给定n个非负整数A[1], A[2], ……, A[n]。
对于每对(i, j)满足1 <= i < j <= n,得到一个新的数A[i] xor A[j],这样共有n*(n-1)/2个新的数。求这些数(不包含A[i])中前k小的数。
注:xor对应于pascal中的“xor”,C++中的“^”。
【输入格式】
第一行2个正整数 n,k,如题所述。
以下n行,每行一个非负整数表示A[i]。
【输出格式】
共一行k个数,表示前k小的数。
【样例输入】
4 5
1
1
3
4
【样例输出】
0 2 2 5 5
【样例解释】
1 xor 1 = 0 (A[1] xor A[2])
1 xor 3 = 2 (A[1] xor A[3])
1 xor 4 = 5 (A[1] xor A[4])
1 xor 3 = 2 (A[2] xor A[3])
1 xor 4 = 5 (A[2] xor A[4])
3 xor 4 = 7 (A[3] xor A[4])
前5小的数:0 2 2 5 5
【数据范围】
第一个数据点,n <= 1000;
第二个数据点,k = 1;
对于40%的数据,n <= 10000; k <= 10;
对于60%的数据,n <= 20000;
对于100%的数据,2 <= n <= 100000; 1 <= k <= min{250000, n*(n-1)/2};
0 <= A[i] < 231
这题实在是……
首先,n^2暴力是很好搞的。
其次,q=1的情况也是可做的。据鸿巨大说要用trie树搞
最后,来讲一种我在考场上想到的很奇怪的算法(反正能A就是爷)
以样例为例,先把所有数转成2进制:
001
001
011
100
我的做法是首先把第一位是0的和第一位是1的分开,那么就形成了两块。注意到同一块中的数相互异或肯定第一位是0(因为第一位相同),而在第一块中取一个再从第二块中取一个作异或,肯定第一位是1。这样我们证明了从同一块中挑选一定是优于跨越这一块从另外一块选一个拿去异或。比如说,001、001、011被划分成一块,100被划分成另一块。那么显然在同块中的异或结果更优。比如001^001=0,001^011=2,都优于100^001=5,100^011=7。那分成2块以后还能不能再这样分成4块呢?答案是肯定的。
那么推广一下:把数据快排,暴力根据前几位的状态建一棵树,根节点是所有数,左节点是开头是‘0’的数的区间(快排后这些数肯定在一个区间内而不是被离散),右节点是开头是‘1’的数的区间。树的第三层分别是开头是‘00’‘01’‘10’‘11’的数的区间……这样树就建完了。它是长这样的:(画的丑勿喷)
那么显然这棵树最坏情况下有31层即log(maxlongint),对于每一层我们都可以用n^2暴力枚举树节点上的答案,然后sort。这样画的层数越多,n^2的平摊效果越好。然而我们要保证解数>=k,所以要一层上面Σ(len[i]*len[i-1]/2)>=k。显然我们可以保存每一层产生的数字对数,从上往下找到第一个小于k的,我们就直接暴力枚举ans。
比如第一层根节点最多产生4*3/2=6个,超过5
第二层最多产生3*2/2=3个,不到5
所以我们只好从根节点枚举,这样效率n^2了
不过数据完全随机且比较大,应该不会出现恶意去卡的情况。要不然对于极限数据比如任何a[i]都=1的直接特判一下。
ps:我和黄巨大交流的时候我跟他讲了一下,然后他写了一个暴力A了,我还要做死地把建树的for改成二分,结果又wa又t一时爽……我还是太弱了
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<cstring> #include<algorithm> #define ll long long #define inf 100000000 using namespace std; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,m,num; int a[100005],b[1000005]; int bin[35],tmp[35][100005]; ll cnt[35]; void solve1() { for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) b[++num]=(a[i]^a[j]); sort(b+1,b+num+1); for(int i=1;i<=m;i++) printf("%d ",b[i]); } void cal(int l,int r,int k) { if(l>=r)return; int tot=0; for(int i=l;i<=r;i++) if(tmp[k+1][i]&bin[k])tot++; int L=l-1,R=l+tot-1; for(int i=l;i<=r;i++) if(tmp[k+1][i]&bin[k])tmp[k][++L]=tmp[k+1][i]; else tmp[k][++R]=tmp[k+1][i]; if(k!=0){cal(l,l+tot-1,k-1);cal(l+tot,r,k-1);} cnt[k]+=(ll)(r-l+1)*(r-l)/2; } void print(int l,int r,int k,int x) { if(l>=r)return; if(k==x) { for(int i=l;i<=r;i++) for(int j=i+1;j<=r;j++) b[++num]=tmp[x+1][i]^tmp[x+1][j]; return; } int tot=0; for(int i=l;i<=r;i++) if(tmp[k+1][i]&bin[k])tot++; if(k!=0){print(l,l+tot-1,k-1,x);print(l+tot,r,k-1,x);} } void solve2() { for(int i=1;i<=n;i++)tmp[31][i]=a[i]; cal(1,n,30); bool flag=0; for(int i=30;i>=0;i--) if(cnt[i]<m){print(1,n,30,i+1);flag=1;break;} if(!flag)print(1,n,30,0); sort(b+1,b+num+1); for(int i=1;i<=m;i++) printf("%d ",b[i]); } int main() { freopen("xorit.in","r",stdin); freopen("xorit.out","w",stdout); bin[0]=1;for(int i=1;i<=31;i++)bin[i]=bin[i-1]*2; n=read(),m=read(); for(int i=1;i<=n;i++) a[i]=read(); if(n<=1000)solve1(); else solve2(); return 0; }除此之外,各位大神们还想出了很多其他做法,我承认自己yy根本做不到。
【题目描述】
给定二维平面上n个整点,求该图的一个直线斯坦纳树,使得树的边长度总和尽量小。
直线斯坦纳树:使所有给定的点连通的树,所有边必须平行于坐标轴,允许在给定点外增加额外的中间节点。
【输入格式】
第一行一个整数n,表示点数。
以下n行,每行两个整数表示点的x,y坐标。
【输出格式】
第一行一个整数m,表示中间节点的个数。
必须满足m <= 10 * n
以下m行,每行2个整数表示中间节点的坐标。
以下n+m-1行,每行2个整数u,v表示编号为u和v的两个节点之间有边直接相连,边的权值为两点的曼哈顿距离,即|x[u] – x[v]|+ |y[u] – y[v]|
输入数据给定的点的编号为1..n,选手输出的中间节点的编号为n+1..n+m
【样例输入】
4
-1 0
0 -1
1 0
0 1
【样例输出】
1
0 0
1 5
2 5
3 5
4 5
【格式说明】
选手可使用test.exe对数据进行测试
在命令行下使用“test x”即可测试rsmtx.in和rsmtx.out
(注:x必须是一个字符,即“test 1234”等价于“test 1”)
【题目描述】
给定二维平面上n个整点,求该图的一个直线斯坦纳树,使得树的边长度总和尽量小。
直线斯坦纳树:使所有给定的点连通的树,所有边必须平行于坐标轴,允许在给定点外增加额外的中间节点。
【输入格式】
第一行一个整数n,表示点数。
以下n行,每行两个整数表示点的x,y坐标。
【输出格式】
第一行一个整数m,表示中间节点的个数。
必须满足m <= 10 * n
以下m行,每行2个整数表示中间节点的坐标。
以下n+m-1行,每行2个整数u,v表示编号为u和v的两个节点之间有边直接相连,边的权值为两点的曼哈顿距离,即|x[u] – x[v]|+ |y[u] – y[v]|
输入数据给定的点的编号为1..n,选手输出的中间节点的编号为n+1..n+m
【样例输入】
4
-1 0
0 -1
1 0
0 1
【样例输出】
1
0 0
1 5
2 5
3 5
4 5
【格式说明】
选手可使用test.exe对数据进行测试
在命令行下使用“test x”即可测试rsmtx.in和rsmtx.out
(注:x必须是一个字符,即“test 1234”等价于“test 1”)
这题前4个点n=8,是状压dp:f[x][y][s]表示当前过点(x,y),点之间的联通性是s的最小边数,然后乱搞
5到8个点是插头dp……根本不会
最后两个点暴搜吧
结果黄巨大写的神rand居然拿下70分,而且最后两个点还把志灿hack了……真不知是该膜拜呢还是膜拜呢还是膜拜