Problem E: So easy
Time Limit:3000/1000 MS (Java/Others) Memory Limit:163840/131072 KB (Java/Others)
Description
“iG牛逼, 登峰造极境!······“ GG同学仿佛还没有从月初iG夺冠的欢喜中醒过来,虽然还在睡梦中, 嘴里却不断的念叨着.不过, GG同学的梦没有做多久, 他被室友的一声大喊吵醒了, “出分了! 出分了! 面向对象编程出分了!”, GG同学打开微信小程序看了一下自己的成绩, 非常的失落. 新的一天就这样从兴奋的梦境跌落到悲伤的现实中. “唉, 没有对象又怎么面向对象编程呢?”
GG同学发明了一个新的游戏, 想要考考同样没有对象但是分数却很高的MM同学. 游戏规则是这样的: 最开始GG同学有一个 n×nn×n 的数组, 并且数组中的每一个元素都是0. 接下来GG同学会做若干次操作, 每次操作他会选择一行或者一列, 将他所选择的这一行(或者列)的所有元素都加上一个正整数. 经过几次操作之后, 他又将一个元素给隐藏了(即将该元素变为−1−1). 现在他想要MM同学告诉他这个元素是多少?
题解:假设h[i],l[j]分别为第i行第j列加的值
设maze[i][j]为-1 则maze[i][j]=h[i]+l[j],maze[i-1][j]=h[i-1]+l[j],maze[i][j-1]=h[i]+l[j-1],maze[i-1][j-1]=h[i-1]+l[j-1]
maze[i][j]= h[i]+l[j]
= (h[i-1]+l[j])+(h[i]+l[j-1])-(h[i-1]+l[j-1])
= maze[i-1][j]+maze[i][j-1]-maze[i-1][j-1];
四个角落特判一下yi'f以防没有i-1和j-1(不过好像数据很水 不判也能A)
代码:
#include<bits/stdc++.h>
using namespace std;
int a[1010][1010];
int main()
{
int n;
scanf("%d",&n);int ni,nj;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
if(a[i][j]==-1)
{
ni=i;nj=j;
}
}
}
if(ni==1&&nj==1)
{
printf("%d\n",a[1][2]+a[2][1]-a[2][2]);return 0;
}
if(ni==n&&nj==1)
{
printf("%d\n",a[n][2]+a[n-1][1]-a[n-1][1]);return 0;
}
if(ni==1&&nj==n)
{
printf("%d\n",a[1][n-1]+a[2][n]-a[2][n-1]);return 0;
}
printf("%d\n",a[ni][nj-1]+a[ni-1][nj]-a[ni-1][nj-1]);
}
Problem F: IG重回DotA2
Time Limit:3000/1000 MS (Java/Others) Memory Limit:294912/262144 KB (Java/Others)
Total Submissions:32 Accepted:8
Description
IG在S8全球总决赛斩获冠军,王校长很是开心,于是决定多投电竞几个亿,来扩充他的IG军团。他格外重视DotA2分部的扩大。
在众多DotA2战队中,王校长格外看好的有nn支队。他决定把其中kk支队买下来,来组建一支超级战队。
每支队都有5名选手,在团队中承担着不同的职责(比如中单、辅助、Carry),可以分别叫作一、二、三、四、五号位。每名选手都有一个能力值vv,代表着他的个人实力。组建超级战队的过程是这样的:对于某号位置,王校长会从已经买下的kk支队中,找出一名能力最强的选手来担任。
王校长想让这支超级战队的5名选手能力值之和尽量大。现在,让你来选择出kk支战队,从而得到这个最大值。
题解:状压dp[i][j] 表示状态j个人选i的状态的最大值 例如i二进制表示为11001,j为3 即为任意3个队选第1 4 5(从右往左)三种能力的最大值。 转移就是几种能力中选几个能力为一个人贡献 剩下为其他人贡献。状压dp自己看不懂很难讲明白...看代码把
代码:
#include<bits/stdc++.h>
using namespace std;
int a[10100][5];//原始数据
int maxx[5];//每种能力最大值
int dp[1010][5]; //dp
int n;int k;
int main()
{
scanf("%d%d",&n,&k);
memset(dp,-1,sizeof(dp));
for(int i=1;i<=n;i++)
{
for(int j=0;j<5;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<5;j++)
{
maxx[j]=max(maxx[j],a[i][j]);
}
for(int ni=1;ni<(1<<5);ni++)//记录一个人的各个状态下最大值
{
int now=0;
for(int j=0;j<5;j++)
{
if((ni>>j)&1==1){
now+=a[i][j];
}
}
dp[ni][1]=max(dp[ni][1],now);
}
}
int ans=0;
if(k>=5)//超过5个人直接每个能力取最大值
{
for(int j=0;j<5;j++)
{
ans+=maxx[j];
}
printf("%d\n",ans);
return 0;
}
for(int i=1;i<(1<<5);i++)//DP转移 i表示当前状态
{
for(int j=2;j<=k;j++){
for(int ni=1;ni<(1<<5);ni++)//ni&i表示一个人占这个状态的最大值 剩下的由其他人提供
{
if((i&ni)!=0){
dp[i][j]=max(dp[i][j],dp[(ni&i)^i][j-1]+dp[(i&ni)][1]);
}
}
}
}
printf("%d\n",dp[(1<<5)-1][k]);
}
Problem G: 光!
Time Limit:3000/1000 MS (Java/Others) Memory Limit:163840/131072 KB (Java/Others)
Total Submissions:12 Accepted:5
Description
在二维欧几里德空间内有一单位正方形,四顶点分别为(0,0),(0,1),(1,1),(1,0)(如图所示)。在这个单位正方形的四条边上放平面镜,镜面朝向正方形内部。计x轴上的平面镜为a,按照顺时针方向其余三边分别为b、c、d。正方形的重心(0.5,0.5)放有一个点光源(不计体积),某一时刻向右侧射出斜率为有理数p/q的光线,当光线遇到平面镜时会反射,问第t次遇到平面镜时位于哪面平面镜上。
题解:将单个格子变成一个无穷大的网格左边第i个格子是原格子i-1次左右翻转,上边同理
那么显然原中心(0.5,0.5)且会经过新中心(0.5+q,0.5+p)。并且经过了p条横线q条竖线。剩下的点暴力枚举判断最后停在的方格数。如果最后一个格是从左边射入第i+1个格子(经过了i条竖线)则i%2==0即为b,i为奇数为d。上下同理。
代码:
#include<bits/stdc++.h>
using namespace std;
int q,p,T,t;
double gety(double x){
return x*p/q;
}
double a[400];
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&p,&q,&t);
int num=t/(p+q);
t%=(p+q);
if(t==0){
if(p>q){
if((num*p)%2==0){
printf("a\n");continue;
}
else{
printf("c\n");continue;
}
}
else{
if((num*q)%2==0){
printf("b\n");continue;
}
else{
printf("d\n");continue;
}
}
}
for(int i=1;i<=p;i++){
a[i]=i-0.5;
}
for(int i=p+1;i<=p+q;i++){
a[i]=gety(i-p-0.5);
}
//暴力算接下来p+q条线y坐标
sort(a+1,a+1+q+p);//排序求第t%(p+q)个
int pp=0,qq=0;
for(int i=1;i<=p+q;i++)
{
if(abs(a[i]-0.5-(int)a[i])<=1e-6)//如果是横线
{
pp++;
}
else//如果是竖线
{
qq++;
}
if(t==i)//如果已经是第 t%(p+q)个了
{
if(abs(a[i]-0.5-(int)a[i])<=1e-6)
{
if((num*p+pp)%2==0){
printf("a\n");break;
}
else{
printf("c\n");break;
}
}
else{
if((num*q+qq)%2==0){
printf("b\n");break;
}
else{
printf("d\n");break;
}
}
}
}
}
}
Problem H: 暖气管道
Time Limit:4000/2000 MS (Java/Others) Memory Limit:294912/262144 KB (Java/Others)
Total Submissions:9 Accepted:4
Description
几年前,大工还有独立的供暖系统。那时候,无论是在宿舍还是教学楼,人常常热得汗流夹背,只好打开窗户。后来,为了响应推进生态文明建设的号召,我们学校决定进行供暖改造,并入大连市内的供暖网络,撤除原有大功耗的供暖锅炉。
整个大工有nn个供暖区,标号1...n1...n。又有mm条供暖管道,每条管道连接uu和vv两个供暖区。每个供暖区的面积是SiSi。
改造的计划是这样的:首先,要保证宿舍区的供暖质量。假设宿舍区的标号是kk。现在要去掉一条供暖管道,使得宿舍区所处的联通块缩小,从而使其总面积变小。联通块的面积越小,供暖的质量越佳。(关于联通块的概念,请百度一下)
所以,请你求出:去掉一条供暖管道后,宿舍区所处联通块的最小总面积。
题解:双联通分量缩点 就变成求树上权值最大的支路了,可能会补吧。
1249: Not XOR but OR
Time Limit:8000/6000 MS (Java/Others) Memory Limit:163840/131072 KB (Java/Others)
Total Submissions:12 Accepted:5
Description
给你一个包含 n 个正整数序列A和一个正整数 k. 你的目标是将这 n 个正整数划分成 k 个不相交连续非空的子序列, 以至于每个元素都恰好属于一个子序列. 每个子序列可以有两个整数 l 和 r (l≤r) 描述, 其含义是A[l],A[l+1],…,A[r]. 这样的一个子序列的价值是所有元素的按位或, 即A[l]|A[l+1]|…|A[r] . 总的价值是所有的子序列的价值和. 现在你必须找道一种划分方式使得总的价值和最大.
如果你的不知道按位或的话, 你能了解更多关于[按位或](https://en.wikipedia.org/wiki/Bitwise_operation#OR).
Input
第一行包括一个正整数 T, 表示测试数据的数目.
接下来 T 组测试数据如下: 第一行包括每组测试数据的两个正整数 nn 和 kk, 表示序列元素的数目和要求划分的子序列数目.
第二行包括 n 个正整数, 表示该序列.
Output
对于每组测试数据, 输出一行, 包含所能达到的最大价值.
Sample Input
4
3 2
1 2 2
4 3
1 2 3 4
2 2
1 2
11 4
66 152 7 89 42 28 222 69 10 54 99
Sample Output
5
10
3
704
HINT
●1≤T≤10
●1≤n≤1000
●1≤k≤n
●1≤A[i]≤2^30
●保证所有组的 n 的和不超过 5000
Source
按何大佬的要求补一下题解ORZ
提示:n只有1000 而且复杂度是O(n^2logn),当时没看范围以为是神仙题就一直没碰,结果发现n范围1000......
题解:设原数组为A数组。很容易想到dp[i][j]表示前i个数分成k组的最大值 ,那么问题在于怎么进行状态转移。
显然dp[i][j]=max(dp[t][j-1]+(A[t+1]|A[t+2]......|A[i]))。但这样转移时O(n)的,总复杂度就变成了n^3。
接着我们可以发现随着dp[i][j]会随着i的值变大(即dp[i][j]>=dp[i-1][j])而(A[t+1]|A[t+2]......|A[i])的值只会变换不超过30次。所以我们取30个变换点进行DP复杂度就变成O(31n^2)了。
代码:
#include<bits/stdc++.h>
using namespace std;
long long dp[1020][1020];//dp[i][j]表示前i个数分成k组的最大值
int a[1020];//原数组
int pos[32];//pos[i]为第i位上次出现的标
int tmp[2][32];
map<int,int> ma;//--------map的key值为出现的下标,value为当前位或运算到那一位的值
int main()
{
int T;
scanf("%d",&T);map<int,int>::iterator iter;
int tt=(1<<30)-1;tt+=(1<<30);
while(T--){
int n,k;
scanf("%d%d",&n,&k);
//---------------初始化
ma.clear();
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
for(int j=0;j<=k;j++){
dp[i][j]=0;
}
}
for(int i=0;i<30;i++){
pos[i]=0;
}
//---------------------
for(int i=1;i<=n;i++){
for(int j=0;j<30;j++){
if((a[i]>>j)&1){
if(pos[j]!=0){//-------如果map里有这个数就减掉相应的数
ma[pos[j]]&=((1<<j)^tt);
if(ma[pos[j]]==0){
ma.erase(pos[j]);
}
}
pos[j]=i;
if(ma.count(pos[j])){
ma[pos[j]]=ma[pos[j]]|(1<<j);
}
else{
ma[pos[j]]=(1<<j);
}
}
}
int ti=0;//--------------因为map默认是按递增排序的,所以这里存进数组倒着用。
for(iter=ma.begin(); iter!=ma.end(); iter++)
{ti++;
tmp[1][ti]=iter->second;
tmp[0][ti]=iter->first;
}//------------------------------------------------------------------------
dp[i][1]=dp[i-1][1]|a[i];
for(int j=2;j<=k;j++){
if(j>i) break;
int now=a[i];
for(int d=ti; d>=1;d--)
{
if(tmp[0][d]-1<j-1)
{
break;
}
now|=tmp[1][d];
dp[i][j]=max(dp[i][j],dp[tmp[0][d]-1][j-1]+now);//------------------------------dp转移关系
}
}
}
printf("%lld\n",dp[n][k]);
}
}